创建多线程的三种方式
在java中给我们提供了三种方式来创建多线程。前两种是我们比较常见的,第三种是JDK1.5之后提供给我们的。接下来我们详细的看一下这三种创建线程的写法。
继承Thread类
第一种方式是继承Thread类的写法,代码如下:
Thread thread = new Thread(){
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("线程创建的第一种方式:"+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
thread.start();
注意这里我们覆盖的是run方法,而不是start方法,并且也千万不要覆盖start方法。为什么不能覆盖start方法呢?我们看一下Thread源码中start方法是怎么写的:
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
注意这个方法是加锁的方法。在这个方法中最重要的一段代码是:start0();我们接着再来看一下start0()这个方法:
private native void start0();
它是个native方法。就是这个native的start0()方法,它实现了启动线程,申请栈内存、运行run方法、修改线程状态等职责。线程管理和栈内存管理都是由jvm负责的。如果你覆盖了start方法,也就是撤销了线程管理和栈内存管理的能力,这样还如何启动一个线程呢?不过Thread的这个设计是很精妙的,因为你只需要关注你的业务逻辑就行了,而对于线程和栈内存的管理都有JVM来做就行了。如果在你的开发中不得不要覆盖start方法的话,请千万要记得调用super.start(),要不然你的线程无法启动。
实现Runnable接口
第二种创建线程的方式是实现Runnable接口。具体代码请看下面:
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("线程创建的第二种方式:"+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread2.start();
这种写法是实现了Runnable接口的一种写法。它的原理是什么呢?我们来看一下Thread源码中run的写法:
@Override
public void run() {
if (target != null) {
target.run();
}
}
在run方法中我们可以看到如果target != null就调用target.run()方法。而这个target是从哪儿来的呢?我们继续看Thread的源码,发现在init的方法中有这样一句话:
this.target = target;
接下来我们再看init()这个方法是在哪儿被调用的?通过翻读源码我们可以发现在Thread的每一个构造函数中都会调用init这个方法,并且有这样一个构造函数:
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
看到了吧。我们就是在创建Thread的时候,通过Thread的构造函数传递进去的Runnable的实现类。而线程启动的时候,调用run方法,run方法又接着调用Runnable实现类的run方法!!!!
实现Callable接口
在JDK1.5之后又给我们提供了一种新的创建线程的方式:实现Callable方法。具体代码如下:
FutureTask<Integer> ft = new FutureTask<Integer>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int i = 0;
for(;i<10;i++){
System.out.println("线程创建的第三种方式:"+Thread.currentThread().getName());
}
return i;
}
});
new Thread(ft).start();
在上面的代码中,在创建FutureTask对象的时候,我们把Callable的一个匿名实现类当做参数传到了FutureTask的构造函数中,而在启动线程的时候,我们又把创建的FutureTask的对象当做参数传到了Thread的构造函数中。在这里请注意我们此时不是覆盖的run方法,而是一个叫call的方法。大家可能会感到疑惑这个FutureTask和Callable这两个到底是个什么玩意?下面我们一个一个的分析:
FutureTask
通过翻读FutureTask的源码我们可以看出来实现了RunnableFuture接口,而RunnableFuture接口又继承了Runnable和Future接口。注意:这里是继承了两个接口!你可能会有疑问JAVA中不是没有多继承吗?不错,java中类是没有多继承的,而对于接口是有多继承的!!!到这里我们明白一件事,那就是FutureTask是Runnable接口的一个实现类。到这里你是不是明白了点什么呢?
public class FutureTask<V> implements RunnableFuture<V>
public interface RunnableFuture<V> extends Runnable, Future<V>
如果你不明白的话,那就多看几遍第二种创建线程的方式和它的原理吧。接下来我们来看一下FutureTask这个类中的run方法是怎么写的:
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
上面这个方法中的代码我没有贴全,只贴出来了主要的部分。在这个方法中我们会发现这样的两句话Callable<V> c = callable;result = c.call();这两句话就是关键!!!通过翻读源码我们就会发现源码这个callable就是我们刚才传到FutureTask中的Callable的实现类啊!
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
c.call()那不就是调用的Callable实现类的call方法吗?!!!到这里终于真相大白了!FutureTask中其他方法有兴趣的同学可以继续研究一下。
Callable
在Callable这个接口中只有一个call方法。
总结
在实际编码中,我们看到创建线程更多的是使用第二种方式,因为它更符合java中面向接口编程的思想。
最后出个题考一下大家,请说出下面代码的运行结果:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Runnable实现类的调用:");
}
}){
@Override
public void run() {
System.out.println("继承Thread的调用:");
}
}.start();