Java基础之线程学习

1. 学习链接

B站视频链接(超详细的讲解)

2. 程序、进程、线程的区别于联系

操作系统中运行的程序就是进程(process);
程序是静态的,跑起来后变成了进程,进程是系统分配的;
进程里面有很多个线程,线程之间不受影响,比如看视频时的画面、声音等;
CPU同一时间段只能做一件事情,但是因为CPU切换的很快,所以产生了同时运行的错觉;
有几个默认线程:
① main线程:系统入口,执行整个程序;
② gc线程:处理垃圾;
线程会带来额外的开销,比如调度时间等;

3. 线程如何创建

(1) 继承Thread类(该类里面实现Runnable接口)

步骤
① 继承Thread类
② 重写run方法
③ 在主线程中用start方法调用(如果是直接调用run方法的话,还是会先执行run方法内容,再执行下面内容,因为没有开出新的线程,而通过start方法会先开出线程,再看CPU调度,去交替执行主线程和子线程内容,也就是说start方法开启了CPU的切换调度
实例1

public class TestThread1 extends Thread{
   
    @Override
    public void run() {
   
        for(int i=1;i<=10;i++){
   
            System.out.println("子线程"+i);
        }
    }

    public static void main(String[] args) {
   
        TestThread1 thread1=new TestThread1();
        thread1.start();

        for(int i=1;i<=1000;i++){
   
            System.out.println("主线程"+i);
        }
    }
}

结果

主线程1
主线程2
主线程3
主线程4
主线程5
主线程6
主线程7
主线程8
主线程9
主线程10
主线程11
主线程12
主线程13
主线程14
主线程15
主线程16
主线程17
子线程1
子线程2
子线程3
子线程4
子线程5
子线程6
子线程7
主线程18
子线程8
子线程9
子线程10
主线程19
主线程20
......(因为内容太多,就只展示一部分结果)
  • 可以明显发现,CPU先是给了主线程(main),让主线程运行到它的i=17时,又通过线程切换CPU给了子线程运行,当子线程的i=7时,又通过线程切换CPU给了主线程运行…
  • 如果不是调用了start方法,而是调用了run方法,那么运行结果将是直接等到run中的i=1~10执行完,才会执行main中的i=1~1000的部分
  • start方法开出的线程不一定同时进行,要看CPU调度

实例2:(多线程同步下载图片)
需要commons-io包里面的FileUtils文件工具类的方法将URL网页地址变成文件保存到本地;包放到lib文件下面,操作方法同在eclipse中;

public class TestThread2 extends Thread{
   
    private String url;
    private String fileName;

    public TestThread2(String url, String fileName) {
   
        this.url = url;
        this.fileName = fileName;
    }

    @Override
    public void run() {
   
        WebDownloader webDownloader=new WebDownloader();
        webDownloader.download(url,fileName);
        System.out.println(fileName+"下载完成!");
    }

    public static void main(String[] args) {
   
        TestThread2 thread1=new TestThread2("https://img02.sogoucdn.com/app/a/100520093/8379901cc65ba509-45c21ceb904429fc-4ac1f58edc3ca2ad43ac5eea3b406500.jpg","headPic1.jpg");
        TestThread2 thread2=new TestThread2("https://img02.sogoucdn.com/app/a/100520093/8379901cc65ba509-45c21ceb904429fc-3cce4e108dc686e431b8d18fbf6e043d.jpg","headPic2.jpg");
        TestThread2 thread3=new TestThread2("https://img02.sogoucdn.com/app/a/100520093/ae588be27ee085c4-fd668f66a830d70e-24b46b127c1c13c86a5320324f49613b.jpg","headPic3.jpg");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

//下载器
class WebDownloader {
   
    //下载方法
    public void download(String url,String fileName){
   
        //调用commons-io包里面的文件工具类,将URL网页地址转为文件
        try {
   
            FileUtils.copyURLToFile(new URL(url),new File(fileName));
        } catch (IOException e) {
   
            e.printStackTrace();
            System.out.println("下载图片失败");
        }
    }
}

思路

  • 先创建下载器(是一个类),在里面创建下载方法(调用commons-io包里面的方法去把url网页地址下载成本地文件);然后run方法里面去调用下载器方法下载,再在主线程里面创建几个子线程,运行主线程看运行结果。
  • 下载位置就是在这个项目下面,如下图:
    在这里插入图片描述
  • 注意, 文件名在传入的时候加后缀

结果

headPic3.jpg下载完成!
headPic1.jpg下载完成!
headPic2.jpg下载完成!

会发现下载的顺序并不是1、2、3,而是3、1、2,说明这个里面发生了CPU的调度,也就是确实开启了子线程,如果我们重新运行的话会发现顺序会发生改变,并不是固定的。

(2) 实现Runnable接口(推荐

步骤
① 实现Runnable接口;
② 重写run方法;
③ 创建Runnable接口实现类对象,将该对象丢掉Thread里面(Thread的构造方法参数是该对象),然后调用Thread对象的start方法(这个叫代理,后面会理解);(这一步和上一中方法不太一样
实例1:(对上一种方法实例1的改写)

public class TestThread3 implements Runnable{
   
    @Override
    public void run() {
   
        for(int i=1;i<=10;i++){
   
            System.out.println("子线程"+i);
        }
    }

    public static void main(String[] args) {
   
        TestThread3 testThread3=new TestThread3();
        new Thread(testThread3).start();

        for(int i=1;i<=200;i++){
   
            System.out.println("主线程"+i);
        }
    }
}

实例2:(对上一种方法实例2的改写)

//public class TestThread2 extends Thread{
   
public class TestThread2 implements Runnable{
   
    private String url;
    private String fileName;

    public TestThread2(String url, String fileName) {
   
        this.url = url;
        this.fileName = fileName;
    }

    @Override
    public void run() {
   
        WebDownloader webDownloader=new WebDownloader();
        webDownloader.download(url,fileName);
        System.out.println(fileName+"下载完成!");
    }

    public static void main(String[] args) {
   
        TestThread2 thread1=new TestThread2("https://img02.sogoucdn.com/app/a/100520093/8379901cc65ba509-45c21ceb904429fc-4ac1f58edc3ca2ad43ac5eea3b406500.jpg","headPic1.jpg");
        TestThread2 thread2=new TestThread2("https://img02.sogoucdn.com/app/a/100520093/8379901cc65ba509-45c21ceb904429fc-3cce4e108dc686e431b8d18fbf6e043d.jpg","headPic2.jpg");
        TestThread2 thread3=new TestThread2("https://img02.sogoucdn.com/app/a/100520093/ae588be27ee085c4-fd668f66a830d70e-24b46b127c1c13c86a5320324f49613b.jpg","headPic3.jpg");

//        thread1.start();
//        thread2.start();
//        thread3.start();
        new Thread(thread1).start();
        new Thread(thread2).start();
        new Thread(thread3).start();
    }
}

//下载器
class WebDownloader {
   
    //下载方法
    public void download(String url,String fileName){
   
        //调用commons-io包里面的文件工具类,将URL网页地址转为文件
        try {
   
            FileUtils.copyURLToFile(new URL(url),new File(fileName));
        } catch (IOException e) {
   
            e.printStackTrace();
            System.out.println("下载图片失败");
        }
    }
}

实例3:(买火车票的例子,三种不同职业的人群都在抢10张票)

public class TestThread4 implements Runnable{
   
    private int num=10;//原票数

    /**
     * @param
     * @return void
     * @author 瞿莹莹
     * @describe: 做不停抢票操作
     * @date 2021/8/4 19:33
     */
    @Override
    public void run() {
   
        while (num>0){
   
            //Thread.currentThread()可以获得当前线程,返回结果是Thread类型,getName可以获得当前线程的名字
            System.out.println(Thread.currentThread().getName()+"抢到了第"+num--+"张票!");
            try {
   
                Thread.sleep(2);//模拟延时
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
   
        TestThread4 ticket=new TestThread4();//一个资源

        //多个线程操作同一资源(会出现问题)[构造方法中可以给线程直接起名字]
        new Thread(ticket,"老师").start();
        new Thread(ticket,"医生").start();
        new Thread(ticket,"黄牛").start();
    }
}

结果

黄牛抢到了第9张票!
老师抢到了第8张票!
医生抢到了第10张票!
黄牛抢到了第7张票!
医生抢到了第7张票!
老师抢到了第7张票!
黄牛抢到了第6张票!
医生抢到了第5张票!
老师抢到了第5张票!
黄牛抢到了第4张票!
医生抢到了第3张票!
老师抢到了第3张票!
黄牛抢到了第2张票!
医生抢到了第1张票!
老师抢到了第1张票!
  • 可以发现这里的结果出现问题,两个人不应该同时抢到同一张票,所以得出结论:多个线程同时操作同一个资源,线程变得不安全,数据紊乱,是线程并发问题,解决办法在后面会解释;
  • Thread.currentThread()可以获得当前线程,返回结果是Thread类型,getName可以获得当前线程的名字
  • 线程类的构造方法中可以给线程起名字
  • 线程类的sleep方法可以模拟延时

推荐使用实现Runnable接口这种方式来实现线程,可以避免Java单继承的局限性;

实例4:(龟兔赛跑)

public class TestThread5 implements Runnable{
   
    private static String winner;//比赛胜利者

    /**
     * @param
     * @return void
     * @author 瞿莹莹
     * @describe: 做跑步操作
     * @date 2021/8/4 19:33
     */
    @Override
    public void run() {
   
        for(int i=1;i<=50;i++){
   //开始跑步
            //每跑一步之前都去看一下有没有产生胜利者
            Boolean tag=isOver(i);
            if(tag){
   //已经产生胜利者时
                break;
            }

            //每跑一步都记录一下
            System.out.println(Thread.currentThread().getName()+"跑了"+i+"步!");

            //如果是兔子在跑步,那么每跑20步就休息一下
            if(Thread.currentThread().getName().equals("兔子")&&i%20==0){
   
                try {
   
                    Thread.sleep(1);
                } catch (InterruptedException e) {
   
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * @param step
     * @return boolean
     * @author 瞿莹莹
     * @describe: 根据步数依据情况判断有没有产生胜利者
     * @date 2021/8/4 20:01
     */
    public boolean isOver(int step){
   
        if(winner!=null){
   //说明已经产生胜利者,那么不需要对步数做判断
            return true;
        }else {
   
            if(step>=50){
   //说明已经产生胜利者,那么对winner变量进行赋值
                winner=Thread.currentThread().getName();
                System.out.println("winner is "+winner);//这句话如果写在这个方法里面的话就只会运行一遍,如果出现在run方法里面,就会运行两遍这行代码
                return true;
            }
            return false;
        }
    }

    public static void main(String[] args) {
   
        TestThread5 run=new TestThread5();//一个资源

        //多个线程操作同一资源(会出现问题)[构造方法中可以给线程直接起名字]
        new Thread(run,"兔子").start();
        new Thread(run,"乌龟").start();
    }
}

结果

兔子跑了1步!
乌龟跑了1步!
乌龟跑了2步!
兔子跑了2步!
乌龟跑了3步!
兔子跑了3步!
乌龟跑了4步!
兔子跑了4步!
乌龟跑了5步!
兔子跑了5步!
兔子跑了6步!
兔子跑了7步!
兔子跑了8步!
乌龟跑了6步!
兔子跑了9步!
兔子跑了10步!
兔子跑了11步!
兔子跑了12步!
乌龟跑了7步!
兔子跑了13步!
乌龟跑了8步!
乌龟跑了9步!
兔子跑了14步!
乌龟跑了10步!
兔子跑了15步!
兔子跑了16步!
兔子跑了17步!
兔子跑了18步!
兔子跑了19步!
兔子跑了20步!
乌龟跑了11步!
乌龟跑了12步!
乌龟跑了13步!
乌龟跑了14步!
乌龟跑了15步!
乌龟跑了16步!
乌龟跑了17步!
乌龟跑了18步!
乌龟跑了19步!
乌龟跑了20步!
乌龟跑了21步!
乌龟跑了22步!
乌龟跑了23步!
乌龟跑了24步!
乌龟跑了25步!
乌龟跑了26步!
乌龟跑了27步!
乌龟跑了28步!
乌龟跑了29步!
乌龟跑了30步!
乌龟跑了31步!
乌龟跑了32步!
乌龟跑了33步!
乌龟跑了34步!
乌龟跑了35步!
乌龟跑了36步!
乌龟跑了37步!
乌龟跑了38步!
乌龟跑了39步!
乌龟跑了40步!
兔子跑了21步!
兔子跑了22步!
兔子跑了23步!
乌龟跑了41步!
兔子跑了24步!
兔子跑了25步!
兔子跑了26步!
兔子跑了27步!
兔子跑了28步!
兔子跑了29步!
兔子跑了30步!
兔子跑了31步!
兔子跑了32步!
兔子跑了33步!
兔子跑了34步!
兔子跑了35步!
兔子跑了36步!
兔子跑了37步!
兔子跑了38步!
兔子跑了39步!
兔子跑了40步!
乌龟跑了42步!
乌龟跑了43步!
乌龟跑了44步!
乌龟跑了45步!
乌龟跑了46步!
乌龟跑了47步!
乌龟跑了48步!
乌龟跑了49步!
winner is 乌龟

进程已结束,退出代码为 0

(3) 实现Callable接口(了解即可)

步骤
① 实现Callable接口
② 重写call方法,这个方法有返回值(以前是重写run方法,是没有返回值的)
③ 创建执行服务(相当于开启一个服务)
④ 提交执行(还有其他方法)
⑤ 获取结果
⑥ 关闭服务
实例:(对多线程下载图片实例的改写)

public class TestThread6 implements Callable<Boolean> {
   
    private String url;
    private String fileName;

    public TestThread6(String url, String fileName) {
   
        this.url = url;
        this.fileName = fileName;
    }

    @Override
    public Boolean call() {
   
        WebDownloader webDownloader=new WebDownloader();
        webDownloader.download(url,fileName);
        System.out.println(fileName+"下载完成!");
        return true;
    }

    public static void main(String[] args) {
   
        TestThread6 thread1=new TestThread6("https://img02.sogoucdn.com/app/a/100520093/8379901cc65ba509-45c21ceb904429fc-4ac1f58edc3ca2ad43ac5eea3b406500.jpg","headPic1.jpg");
        TestThread6 thread2=new TestThread6
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值