一、开启线程的3种方式
1.继承Thread类
public class MyThread extends Thread{
public void run(){
//线程运行主体
}
}
class MainDemo{
public static void main(Stirng args[]){
Mythread mythread=new MyThread();//创建一个线程实例,此时就已经为改线程分配好了内存空间。
mythread.start();//启动改线程,启动之后,会自动调用run()方法内容。
}
}
继承
Thread
类,需要重写run()
方法。缺点,由于Java是单继承的,继承
Thread
类后,实现类无法再继承其他类。并且通过继承方式实现,线程数据无法实现数据共享。
2.实现Runnable接口
public class MyThread implements Runnable{
public void run(){
//线程运行主体
}
}
class MainDemo{
public static void main(Stirng args[]){
Mythread my=new MyThread();//创建一个线程实现类实例
//创建一个线程实例(实现Runnable接口的类实例作为参数),此时就已经为改线程分配好了内存空间。
Thread thread1=new Thread(my);
//再创建一个线程实例(实现Runnable接口的类实例作为参数)。
Thread thread2=new Thread(my);
thread1.start();//启动线程1,启动之后,会自动调用run()方法内容。
thread2.start();//启动线程2,
}
}
实现
Runnable
接口,也需要重写run()
方法优点相比较继承
Thread
的方式,实现Runnable
方式更加灵活。可以实现多个线程,跑同一个run()
方法。以达到多个线程共享一份数据的目的。
3.实现Callable接口
public class MyThread implements Callable<String>{
public String call(){
//线程运行主体
}
}
class MainDemo{
public static void main(Stirng args[]){
//创建一个线程实现类实例
Mythread my=new MyThread();
//创建一个FutureTask()对象实例,参数为实现了Callable接口类的实例。
FutureTask<String> fu=new FutureTask<String>(my);
fu
//创建一个线程实例(FutureTask类实例作为参数),此时就已经为改线程分配好了内存空间。
Thread thread=new Thread(fu);
//启动线程
thread.start();
//线程启动运行后,可以通过FutureTask类的get方法,取得该线程call()方法的运行返回值。
//call()的本质也就是run()方法
String result=fu.get();
}
}
实现
Callable
接口,需要重写call()
方法。并且实现时需要设置泛型,这个泛型也就是call()
方法的返回值类型。与其他两种方式最大的不同在于可以给线程运行的主体(
call()
方法)添加一个返回值,并且可以通过一个get()
方法取得这个值。get()
方法由FutureTask
类提供。
二、从源码角度简单分析这三种实现方式
1.Runnable接口原理
Runnable
接口,该接口只有一个方法run()
。通过实现
Runnable
接口的方式,启动线程时,还是需要一个Thread
类的实例,因为只有Thread
类才有与线程操作相关的方法。将
Runnable
实现类的实例作为参数传递给Thread
实例,实际上就是将一个重写好的run()
方法提供给了Thread
实例去执行。在
Thread
类中,有一个Runnable
类型的成员变量target
。Thread
中的run()
方法判断target
不为空时,就会执行target
的run()
方法。如果为空,不会有任何操作。
2.Thread类原理
Thread
类,该类实现了Runnable
接口,并重写了run()
方法。但是线程相关的许多操作都是再该类里面定义的。如
start()
、sleep()
等。一个类继承了
Thread
类之后,会重写它的run()
方法,该类调用Start()
方法启动线程之后,线程会执行本类的run()
方法。
3.Callable接口原理
Callable
接口,该接口只有一个call()
方法。作用与run()
方法类似,都是一个线程的运行主体。但线程的最终运行过程是:
1.实现了
Runnable
接口的FutureTask
类中重写的run()
方法种调用了Callable
接口实现类的call()
方法。2.然后
Thread
类调用FutureTask
类中的run()
方法。这一步与第一种
方式的原理类似。因为FutureTask
类实现了Runnable
接口。
4.总结
Java实现多线程从方式上来说,虽然有3种,但起本质其实都是
Thread
类,不管那种方式,最终都会回到Thread
类。另外两种方式,只是在最本质的一种方式上的封装。给Java多线程机制提供了更多的可能。否则只有继承Thread
这一种方式,未免太过于局限性。对于
Runnable
接口,它就像一个生产集装箱的工厂,我们把一个线程要执行的方法体放在里面一个集装箱里面。对于
Thread
类就像一个卡车。集装箱放上去,它只管带着集装箱跑就行了,当然如果其他人不提供集装箱,Thread
这个卡车,自己也有一个集装箱,不过这个集装箱也是从Runnable
工厂里出来的,但是它是空的,需要我们自己填满。对于
Callable
方式,就相当于一个叫FutureTask
的商人找了Runnable
工厂定制了一种集装箱,这种集装箱上面有一个小口口,可以从集装箱里面拿东西出来。但是还是离不开Thread
卡车。上面说提到的工厂,绝不代表工厂设计模式。 只是我举例,与其他什么设计模式无关联,因为设计模式我完全还不懂。