[TOC]
Java入门类和接口
Thread
类和Runnable
接口
Java中,JDK提供了Thread
类和Runnable
接口,我们有两种方法来实现自己的线程类。
- 继承
Thread
类,并重写run
方法。 - 实现
Runnable
接口的run
方法。
继承Thread
类
1 | public class MyThread extends Thread { |
注意:一定要调用start()
方法该线程才算启动。
new
一个Thread
,线程进入新建状态;调用start()
方法会启动一个线程并使线程进入就绪状态,当分配到CPU时间片就可以运行了。start()
方法会执行线程的相应准备工作,然后自动执行run()
方法的内容,这才是真正的多线程。而直接调用run()
方法,会把run()
方法当成一个main
线程下的普通方法去执行,还是在主线程中。
多次调用start()
方法会抛出java.lang.IllegalThreadStateException
异常。
实现Runnable()
接口
Runnable()
接口源码如下:
1 |
|
可以看到Runnable
接口是一个函数式接口,这代表着可以使用函数式编程简化。
1 | public class ThreadTest { |
不采用lambda
要这样用:
1 | public class ThreadTest { |
Thread
类构造器
Thread
类是Runnable
接口的实现类,其构造方法源码如下:
1 | /** |
可以看到Thread
类的构造器会调用私有的init()
方法来实现初始化。init
方法的方法签名如下:
1 | private void init(ThreadGroup g, Runnable target, String name, |
init
方法参数的解释:
g
:线程组,制定这个线程在哪个线程组下target
:指定要执行的任务;name
:线程的名字,多个线程的名字是可以重复的。如果不指定名字,会默认指定为"Thread-" + nextThreadNum()
。acc
:用于初始化私有变量inheritedAccessControlContext
。inheritThreadLocals
:可继承的ThreadLocal
,Thread
类中有两个私有属性来支持ThreadLocal
。
大多数情况下我们调用下面两种构造方法:
Thread(Runnable target)
Thread(Runnable target, String name)
Thread
类的常用方法
currentThread()
:静态方法,返回对当前正在执行的线程对象的引用。start()
:开始执行线程的方法,Java虚拟机会调用线程内的run
方法。yield()
:当前线程愿意让出对当前处理器的占用。要注意的是,就算当前线程调用了yield()
方法,程序在调度的时候,也还有可能继续运行这个线程的。sleep()
:静态方法,使当前线程睡眠指定时间。join()
:使当前线程等待另一个线程执行完毕后再继续执行,内部调用的是Object类
的wait()
方法。
Thread
类与Runnable
接口的比较
实现一个自定义线程类,可以有继承Thread
类和实现Runnable
接口两种方法。
- 由于Java单继承、多实现的特点,
Runnable
接口使用更加灵活; Thread
类就是Runnable
接口的实现;Runnable
接口更加符合面对对象,将线程单独进行对象的封装;Runnable
接口的出现,降低了线程对象与线程任务的耦合性;- 如果不需要使用
Thread
类的其他方法,使用Runnable
接口更加轻量
综上,我们通常优先使用Runnable
接口来自定义线程类。
Callable
、Future
与FutureTask
通常我们使用Runnable
和Thread
来创建一个线程,但是run
方法是没有返回值的。如果我们希望执行一个任务后返回一个值,我们可以使用JDK提供的Callable
接口和Future
类。这也是所谓的“异步”模型。
Callable
接口
Callable
接口与Runnable
接口类似,同样是一个函数式接口,不同的是Callable
接口有返回值且支持泛型。
1 |
|
Callable
接口一般是配合线程池工具ExecutorSevice
来使用的。ExecutorService
可以使用submit
方法来让一个Callable
接口执行,它会返回一个Future
,我们后续的程序可以通过这个Future
的get
方法得到结果。一个简单的demo如下:
1 | //自定义Callable |
Future
接口
Future
接口的源码如下:
1 | public interface Future<V> { |
cancel
方法是试图取消一个线程的执行。注意是试图取消,不一定取消成功。因为任务可能已完成、已取消或者不可取消。最后返回是否取消成功。
参数mayInterruptIfRunning
代表是否采用中断的方式取消线程执行。
所以有时候,为了让线程有能够取消的可能,就会使用Callable
代替Runnable
。若只是为了可取消性,而不需要结果,可以声明Future<?>
形式类型,并返回null
作为结果。
FutureTask
类
Future
接口有一个实现类FutureTask
,这个类实现了RunnableFuture
接口,而RunnableFuture
接口同时继承了Runnable
接口和Future
接口。
RunnableFuture
接口源码如下:
1 | public interface RunnableFuture<V> extends Runnable, Future<V> { |
Future
只是一个接口,里面的cancel
、get
、isDone
等方法自己实现会很复杂,所以JDk提供了一个FutureTask
类。使用示例:
1 | public class ThreadTest implements Callable<Integer> { |
这里调用submit
方法是没有返回值的。这里实际上调用的是submit(Runnable task)
方法,而上面那个demo中,调用的是submit(Callable<T> task)
方法。
这里是使用futureTask
直接get
取值,而上面Demo中是使用返回的Future
去取值。
在高并发的情景下,有可能Callable
和FutureTask
会创建多次。FutureTask
能确保在高并发下任务只被执行一次。
FutureTask
的几个状态
1 | /** |
state
表示任务的运行状态。初始状态为NEW
。运行状态只会在set
、setException
、cancel
方法中终止。COMPLETING
、INTERRUPTING
是任务完成后的瞬时状态。