在前面的文章叙述中,有一句很明确的表达,java
中只有Thread
对象代表一个线程对象,
一个线程像一个工人一样,按照我们提供的说明书一句一句的读,并按照说明书说的去做,
Runnable
或 Callable
其实就是一份说明书。
new Thread() {
@Override
public void run() {
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
new Thread(() -> {
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
当我们开启了两个线程以后,可以通过idea里的调试器看到,分别有一个Thread-0
和Thread-1
的线程,
这两个线程其实就是我们刚刚new出来的两个线程,为什么我们new出来的线程名字是Thread-0|1
?
通过构造函数可以看到,jvm帮我们将线程名称默认以Thread-
开头加上一个数字,作为默认的名称。
通过观察Thread-0
这个线程,我们可以看到该线程的最底层方法是Thread.run
,事实上所有开启的新线程,方法栈的底部一定都是Thread.run
。
通过查看run
方法的源码,我们可以看到,该方法中,判断target
(Runnable类型),是否存在,如果存在则执行target
的run
方法,
target
是一个Runnable
接口,接口在java中是一种规约,是一个可以被执行的东西,那么这个target
是从哪里来的呢?
我们看一下刚刚创建线程的代码,
查看该构造函数,可以看到这个构造函数接收的参数就是一个Runnable
,
也就是说其实这个Runnable
是我们构建的一个Runnable
的匿名接口实现类,这个实现类其实就是一份代码说明书, new Thread
是新招来的一个工人,
我们把新工人招来后,给了他一份说明书,让他对着说明书去执行任务。
到这里我们可以总结一下,Runnable
其实就是一个任务说明,可以被任意一个线程进行执行,这是他们的区别,是任务
与线程
的区别, Runnable
是用来被Thread
执行的。
Runnable
已经说完了,那Callable
是做什么的 ?
Runnable
有一个弊端,因为它的run
方法是没有返回值的,但是如果我们有一个需求,当一个任务执行完成之后,希望拿到它的返回值呢?
当然通过其他的方式,我们也许可以拿到Runnable
的返回值,例如定义一个全局的Map
或者其他类,当这个Runnable
执行结束的时候,调用一下这个Map
或者其他的类,用于保存结果,但是这种方式很不优雅,
这是Runnable
的限制之一,
同时因为接口的限制,Runnable
的实现类,不可以抛出checked exception
, 因为该方法定义时,没有声明将可能抛出的异常,所以该接口的实现也不可以抛出,只能在实现内部自己消化, 这是限制之二,
下面可以看下Callable
的源码,可以看到有一个call
方法,同事带有返回值,并且允许抛出异常,
Runnable
和Callable
的作用相同,都是一个任务,只是二者一个有返回值一个没有,一个可以抛出异常另一个不可以,
他们都属于被Thread
执行的类,这是他们三者之间的关系和区别,这两个接口在线程池中会有很大的作用,后续再继续讲解。