多线程创建
一、获得多线程的方法有几种?
共有四种。传统的是继承thread类和实现runnable接口,java5以后又有实现callable接口和java的线程池获得。
二、继承Thread类
1、步骤
1. 自定义一个类继承Thread类。
2. 重写Thread类的run方法 , 把自定义线程的任务代码写在run方法中
3. 创建Thread的子类对象,并且调用start方法开启线程。
疑问: 重写run方法的目的是什么?
每个线程都有自己的任务代码,jvm创建的主线程的任务代码就是main方法中的所有代码, 自定义线程的任务代码就写在run方法中,自定义线程负责了run方法中代码。
2、注意:
一个线程一旦开启,那么线程就会执行run方法中的代码,run方法千万不能直接调用,直接调用run方法就相当调用了一个普通的方法而已并没有开启新的线程。
原因:
查看start方法源码,
Start(){
….
start0(); //是一个本地方法,其调用了run方法。
}
这是一种模板设计模式,类似netty中的继承一些入栈handler,重写读和写方法一样。
public void run() {
if (target != null) {
target.run();
}
}
注意:这里的target是Thread维护的一个Runnable接口的引用,Runnable target.
如果从构造函数中传来了Runnable接口的实现类,那么target会引用到这个实现类,在run方法中调用了实现类的run。
如果没有传入runnable,那么就要重写这个run方法,由JVM调用。
三、实现runnable接口
注意:runnable不是线程,线程只有Thread。runnable只是一个接口 ,可以把线程的任务单独提出来。runnable将可执行的逻辑单元和线程控制分离开来,这也是面向对象思想的体现。(将Runnable实现类对象定义为final,即多个线程操控的是同一个Runnable实现类对象,同一份业务单元,当然,这里也会存在线程安全问题,后续再讲)
1、步骤
1. 自定义一个类实现Runnable接口。
2. 实现Runnable接口(只有一个run方法)的run方法,把自定义线程的任务定义在run方法上。
3. 创建Runnable实现类对象。
4. 创建Thread类的对象,并且把Runnable实现类的对象作为实参传递。
5. 调用Thread对象 的start方法开启一个线程。
public class TestDemo implements Runnable {
public void run() {
}
public static void main(String[] args) {
new Thread(new TestDemo()).start();
}
}
四、实现Callable接口,本质还是runnable
1、步骤
1.自定义一个类实现callable接口
2.实现call方法
3.创建futuretask对象,把自定义类当做构造参数传进去
4.创建一个线程 new thread,把futuretask当做构造参数传进去。
5.调用futuretask的run方法。
public void run() {
…..
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
执行完毕后,把返回值set到result中。
set(result);
}
} finally {
…..
}
}
class MyThread implements Runnable{
@Override
public void run() {
}
}
class MyThread2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("******come in call method()");
return 1080;
}
}
/**
* 多线程中,第3种获得多线程的方式
*/
public class CallableDemo7 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTask = new FutureTask(new MyThread2());
new Thread(futureTask,"A").start();
//get方法放到最后,否则会阻塞主线程的执行,影响效率
Integer result = futureTask.get();//获得call()方法的返回值
System.out.println(result);
}
}
Thread的构建只有利用Runnable接口,因此需要Runnable接口和Callable接口的“中间人”,即FutureTask类,实现了Runnable的子接口,同时可以接受Callable接口实现类的形参。因此,即可使用Callable接口生成线程。本质上还是利用了Java的多态性质。
FutureTask<Integer> ft = new FutureTask<Integer>(new MyThread());
new Thread(ft, "AA").start();
运行成功后如何获得返回值?
ft.get();
FutureTask实现了callable和ruannable接口,其run方法本质还是runnable的run方法。thread调用的是futuretask的run方法。只不过futuretask封装了返回值以及一些超时方法。
五、线程池(略)
六、Runnable与Callable接口的区别
创建新类MyThread实现runnable接口
class MyThread implements Runnable{
@Override
public void run() {
}
}
新类MyThread2实现callable接口
class MyThread2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return 200;
}
}
面试题:runnable接口与callable接口的区别?
- 落地方法不一样:一个是run,一个是call
- 是否有返回值:前者run()方法无返回值,后者call()方法有返回值
- 是否抛异常:前者没有异常,后者需要处理异常
七、new Thread()与Runnable接口建立线程的区别
Thread本来就是实现了Runnable,包含Runnable的功能是很正常的啊!!至于两者的真正区别最主要的就是一个是继承,一个是实现;其他还有一些面向对象的思想,Runnable就相当于一个作业,而Thread才是真正的处理线程,我们需要的只是定义这个作业,然后将作业交给线程去处理,这样就达到了松耦合,也符合面向对象里面组合的使用,另外也节省了函数开销,继承Thread的同时,不仅拥有了作业的方法run(),还继承了其他所有的方法。综合来看,用Runnable比Thread好的多。
runnable接口的作用本身是一种策略模式的体现。
八、FutureTask
FutureTask的异步调用
FutureTask,用它就干一件事,异步调用。
main方法就像一个冰糖葫芦,一个个方法由main串起来。但解决不了一个问题:正常调用挂起堵塞问题。
例子:
(1)老师上着课,口渴了,去买水不合适,讲课线程继续,我可以单起个线程找班长帮忙买水,
水买回来了放桌上,我需要的时候再去get。
(2)4个同学,A算1+20,B算21+30,C算31*到40,D算41+50,是不是C的计算量有点大啊,
FutureTask单起个线程给C计算,我先汇总ABD,最后等C计算完了再汇总C,拿到最终结果
(3)高考:会做的先做,不会的放在后面做
FutureTask原理
在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成,当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态。
一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。因此get方法常放到最后。
仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,就不能再重新开始或取消计算。get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常。
注意:
只计算一次,缓存上次结果
get方法放到最后,否则会阻塞主线程的执行,影响效率
代码
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
class MyThread implements Runnable{
@Override
public void run() {
}
}
class MyThread2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+"come in callable");
return 200;
}
}
public class CallableDemo {
public static void main(String[] args) throws Exception {
//FutureTask<Integer> futureTask = new FutureTask(new MyThread2());
FutureTask<Integer> futureTask = new FutureTask(()->{
System.out.println(Thread.currentThread().getName()+" come in callable");
TimeUnit.SECONDS.sleep(4);
return 1024;
});
FutureTask<Integer> futureTask2 = new FutureTask(()->{
System.out.println(Thread.currentThread().getName()+" come in callable");
TimeUnit.SECONDS.sleep(4);
return 2048;
});
new Thread(futureTask,"zhang3").start();
new Thread(futureTask2,"li4").start();
//System.out.println(futureTask.get());
//System.out.println(futureTask2.get());
//1、一般放在程序后面,直接获取结果
//2、只会计算结果一次
while(!futureTask.isDone()){
System.out.println("***wait");
}
System.out.println(futureTask.get());
System.out.println(Thread.currentThread().getName()+" come over");
}
}
/**
*
*
在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成,
当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态。
一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,
就不能再重新开始或取消计算。get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,
然后会返回结果或者抛出异常。
只计算一次
get方法放到最后
*/