并发编程基础篇——第二章(如何创建线程)
Posted 风清扬逍遥子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并发编程基础篇——第二章(如何创建线程)相关的知识,希望对你有一定的参考价值。
上节讲了基础概念,本章正式进入线程专题,对基础薄弱的同学可以好好看本章!!
1、Thread匿名子类
我们可以通过下面的代码来直接创建一个线程。
// 构造方法的参数是给线程指定名字,推荐 Thread t1 = new Thread("t1") @Override // run 方法内实现了要执行的任务 public void run() log.debug("hello"); ; t1.start();
需要注意下:
- 创建线程是不是立马就执行?
当然不是,当调用t1.start()的时候,虚拟机先执行main方法,所以会有main线程去执行代码,而执行到Thread的时候,发现要开辟一个线程,于是会创建一个新线程,等待cpu分配时间片去调度,而start方法两个作用:新创建个线程+此线程进入就绪状态等待被执行。
2、实现Runnable接口
我们也可以通过这个方式去创建线程:
Runnable runnable = new Runnable() public void run() // 要执行的任务 ; // 创建线程对象 Thread t = new Thread( runnable ); // 启动线程 t.start();
和第一种方式相比,其实有很大的不同,这种方式是【任务】和【线程】分开,Runnable实际在这里表示可运行的任务,比如下面的代码执行:Thread一定是要有的,而Thread的有参构造,传入当前要执行的任务对象包给这个线程去执行,同时也可以给予开辟的线程名称。
在Java8的Lambda表达式中,可以简化写法,这里我不多说Lambda表达式自行了解。
我们分析Thread的源码,理清它与 Runnable 的关系,当Runnable对象不为空,Thread构造方法传递Runnable对象,底层会调用Runnable对象的run方法进行任务的创建。
而直接new Thread的方式实际就是创建一个子类对象,重写父类的run方法,最终会调用子类中的run方法,所以在底层执行中,走的都是底层的run方法。
区别在于:
new Thread是把线程和任务合并在了一起
实现Runnable是把线程和任务分开了,用 Runnable 更容易与线程池等高级 API 配合
用 Runnable 让任务类脱离了 Thread 继承体系,更灵活。
3、FutureTask配合Thread
先看下FutureTask的结构:很明显实现了Runnable接口,也是个任务对象;另外多了一个Future接口,用来返回任务的执行相关信息的。
由于Runnable的run方法是void类型,并不能在两个线程中把一个结果传递给另一个线程,而Future接口中的get方法是可以用来返回结果,怎么用这个FutureTask创建一个任务对象呢?
因为FutureTask<V>是个泛型,且get方法返回也是个泛型,在FutureTask的构造中传递参数是Callable接口;
其中的call方法是具备返回值的。
于是我们可以这么写,这里有个比较关键的一个点,当主线程运行到get的时候,会阻塞等待,直到这个task3执行完后返回了,主线程才可以拿到task3执行后返回的结果并输出。
// 创建任务对象 FutureTask<Integer> task3 = new FutureTask<>(() -> log.debug("hello"); return 100; ); // 参数1 是任务对象; 参数2 是线程名字,推荐 new Thread(task3, "t3").start(); // 主线程阻塞,同步等待 task 执行完毕的结果 Integer result = task3.get(); log.debug("结果是:", result);
4、思考为什么有了Thread还出现Runnable?
我觉得这个问题会有很多基础不扎实的同学搞不明白,甚至工作很多年的都会是一脸蒙蔽,我这里就说说我个人的观点:
首先Thread出现的时候,在使用方面,通过继承的方式去重写父类Thread的run方法,达到线程执行逻辑的目的,那么如果一个类A已经继承过了其他的父类B,你再想去让某个线程在这个类执行里面部分代码,你该怎么做?
要么你选择单独开个类C,继承Thread,然后类A的逻辑cp一份到这个C类上走下Thread.start方法;
要么改逻辑?不现实吧,那么Runnable出现,这个时候解决了这个问题,没错,Java中不能多继承,只能单继承的机制,但是可以多实现,好了我可以选择让当前这个A类implements Runanble,并且可以重写里面的run方法,而要执行的代码逻辑,是不是可以抽出一个方法,并且在这个run中调用这个方法就可以了?这是一点
还有就是,Thread t1 = new Thread子类,这个子类里会复写父类的方法,完成业务逻辑,发现没有,这个线程只能干这个事情,没法做到和其他线程共享处理某个任务,不信你就可以试试看。如果我想做多个线程处理同一个业务逻辑,我无法做到,比如,多个窗口卖票!
那么有了Runnable的好处,我可以定义某个任务,这个任务就是去扣除票的,那么Thread提供了相关的Runnable的构造,就表示可以有多个Thread执行这个任务,就可以达到多个线程执行相同的任务效果,至于其中的临界资源控制,不在这个范围中。
所以,好处一定是存在,但是认不认识到,那是另一个事情。
创建线程的三种主要方式就介绍到这。后面剖析线程原理
以上是关于并发编程基础篇——第二章(如何创建线程)的主要内容,如果未能解决你的问题,请参考以下文章