实现一个线程,我在初学Java的时候,听到老师讲的方式,有三种,继承Thread,实现Runnable,实现Callable。网上有的文章说是四种,多出来一个线程池。而有的则说是只有1种。于是我对于探究线程的实现方式到底有几种有了点兴趣。
1,继承Thread
实现线程的第一种方式,继承Thread类,并重写run方法。
这一种的没什么好说的。
2,实现Runnable接口
第二种方法,找一个类,实现Runnable接口,重写run方法,并且将这个实现类的实例,丢到new Thread()的构造函数中去。
从中可以看出,这种方式依然依赖于new Thread()来创建线程,实现了Runnable接口的MyRunnable甚至都没有start方法。MyRunnable只是作为构造函数的参数参与了Thread的构造。
那么这个MyRunnable做了什么呢。
一路点下去会在这里发现端倪
可以看到,实际上将MyRunnable作为new Thread()的参数的作用,就是将Runnable接口赋值给target,指定这个Thread要的运行任务什么。
也就是说,实现Runnable,本质上也只是new Thread
3,实现Callable
第三种方法,实现Callable接口,并重写call方法。Callable与Runnable的区别是Callable有返回值,并且可能抛异常。而Runnable没有返回值。
与Runnable不同,Runnable没有返回值,因此可以用同步的方式使用,start后不会阻塞,可以继续start下一线程或者做其他事。Callable由于有返回值,如果继续使用同步的方式,在返回结果之前会阻塞,如果此时需要建立多个线程,那么就要一个一个的等待线程执行结束。而现在的计算机都是多核cpu,支持多线程同时操作。因此Callable常与异步Future结合使用。这里的演示也是结合了Future进行演示(FutureTask是Tuture接口的实现)。
可以看到,其实Future也是作为一个参数去参与构建Thread,而且构造函数和Runnable的那个是同一个。因为Future实现了RunnableFuture接口,而RunnableFuture接口继承了Runnable接口和Feature接口。换句话说,Future实现了Runnable接口。
Runnable方法中已经说到,Runnable构造Thread其实就是通过给Thread中的Runnable target变量赋值,告诉线程run方法要做什么。而在FutureTask中,重写了RunnableFuture的run方法,它的run方法中,会调用call方法,也就是之前实现Callable接口时重写的线程要做的具体的任务的call方法。
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
//在构造FutureTask时会将state置为NEW
if (c != null && state == NEW) {
V result;
boolean ran;
try {
//这里执行call方法
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
这样就串联起来了,其实还是通过new Thread,指定Runnable的实现类的引用为target,指定run方法。而FutureTask的run就是调用了call方法。因此我们可以说,这种方法其实还是通过new Thread去构建线程。
4,线程池
线程池这个就比较简单了。我们知道,线程池本身不是一种创建线程的方式,它只是会将创建好的线程进行管理。本质上还是要靠前三种方式去实现线程。因此它也不是实现线程的方式。
总结
由上述可以得知,真正能创建线程的方式,本质上只有new Thread()一种。不过表现出来不同的方式而已,不同new Thread的方式有三种,普通的new Thread,实现Runnable,实现Callable。不包括线程池,线程池只是管理线程,将通过前三种方式创建好的线程交给你使用并回收,创建线程仍然依赖于前三种方式。