前言
广义上讲,JAVA实现多线程有三种方式:继承Thread类、实现Runnable接口、实现Callable接口利用FutureTask类,本质上来说,最终都要通过 new Thread(…).start();来实现,也就是说最终的多线程还是由Thread类来实现的,那么其它两种方式出现的目的是什么呢?因为Thread继承的直接方式具有一定的局限性,另外两种方式在Thread的基础上利用优秀的设计模式(装饰器、适配器模式)对Thread进行了功能扩展**
/**
*构造方法, 这里并没有 Callable 或者 FutureTask参数,那么Thread是怎么实现第三种方式的呢,下面再细说
*/
public Thread(String name) {
init(null, null, name, 0);
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
Thread
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
public class MyThread extends Thread{
@Override
public void run() {
//本身就是Thread类型对象,所以可以直接调用Thread提供的方法
this.currentThread();
System.out.println("这是继承自Thread类的子类创建的线程, 线程名字: " + this.currentThread().getName());
}
}
源码:最终会调用一个 start0 的本地方法来创建多线程(start0方法的具体实现是JVM源码 C、Cpp代码实现一系列多线程操作,这里不做阐述)start方法是真正意义的启动多线程,多线程启动后,会回调run方法执行用户自己的逻辑,这个具体调用,什么时候调用也是JVM源码去做的,另外 Thread 继承Runnable接口,重写了run方法,如果我们是用的Thread的子类来执行多线程,因为子类也重写了该方法,那么就会回调子类的run方法
public class Thread implements Runnable {
public synchronized void start() {
//start真正执行多线程的方法
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0(); //最终是调用了一个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 */
}
}
}
private native void start0();
/**
* 重写父类run方法,start开启一个线程后会回调该方法
*/
@Override
public void run() {
//...因为继承Thread的子类重写了该方法,这里具体的源码部分不做展示
}
}
Runnable接口
第2种方式是实现Runnable接口,Runnable接口只是提供了一个run方法,没有具体实现,我们可以在run方法里面执行自己的逻辑,
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
new Thread(myRunnable).start();
}
public class MyRunnable implements Runnable{
public void run() {
System.out.println("这是实现Runnable接口的实现类创建的线程, 线程名字: " + Thread.currentThread().getName());
}
}
(1)既然最终还是执行的Thread中的方法调用,那么为什么还要用Runnable接口呢,有什么好处呢?
Thread是类,JAVA是单继承,如果继承了Thread类就不再能扩展了,不利于程序设计,所以又提供了一个Runnable接口
(2)Runable接口是如何和Thread关联起来的呢
在上面Thread源码中我们看到 Thread继承了Runnable,重写了run方法,而且上面我们知道,strat()成功开启一个线程后,JVM会回调run方法,那么就会执行run里面的逻辑
(3)这里其实用到了装饰器模式,装饰器 Thread 和被装饰的类 Runnable子类都实现了Runnable接口,重写同一个方法,Runnable实现类作为属性注入到装饰器中,装饰器调用该方法,最终调用注入的Runnable接口的该方法
/**
* Thread 继承了Runnable接口 重写父类run方法,start开启一个线程后会回调该方法,执行里面的逻辑,这个target其实就是
* 一个Runnable实现类(多态),
*/
public class Thread implements Runnable {
/* What will be run. */
private Runnable target;
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
FutureTask、Callable
Runnable接口解决了单继承了问题,提供了良好的扩展性,但是问题又来了,不管Thread、还是Runnable,都存在一些问题
(1)线程一旦启动就不能停止了
(2)都没有返回值,如果我们想要执行一个有返回值的线程,就要自己实现返回值的获取(可以使用共享变量)
public static void main(String[] args) {
MyCallable myCallable = new MyCallable();
FutureTask<String> futureTask = new FutureTask<String>(myCallable);
new Thread(futureTask).start();
String s = null;
try {
// FutureTask的get()方法和主线程是同步的,因为想要获得Callable线程的返回值,就必须等到Callable线程执行结束
s = futureTask.get();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("Callable方式的返回值: " + s);
} catch (ExecutionException e) {
e.printStackTrace();
}
}
public class MyCallable implements Callable<String>{
public String call() throws Exception {
try {
Thread.sleep(1000 * 60);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("这是实现Callable接口的FutureTask方式创建的线程,线程名字: " + Thread.currentThread());
return "hello";
}
}
(1)最终执行多线程都是由,Thread提供的start方法来执行,那么,Futuretask、Callable是如果实现的多线程呢?,xia’m 源码我们只摘出来其中一部分,因为FutureTask实现了RunnableFuture , RunnableFuture 又继承了Runnable接口,所以,Thread构造方法中我们就可以传递Runnable接口的子类FutureTask,最终调FutureTask用子类重写RunnableFuture 的run方法,而在FutureTask的run方法中,我们最终又调用了属性Callable的call方法,最终实现多线程
(2)在FutureTask的run方法中,除了执行属性Callable的run方法,我们还做了进一步扩展,比如当call方法执行完毕后初始化result返回值属性,从而得到返回值
(3)设计模式——>适配器模式,这里是Callable接口和Runnable接口,本身无关联,通过Runnable接口的实现类的run方法中调用call方法,最终实现关联,这是典型的适配器模式,把Callable call接口和Runnable run接口进行适配并做扩展,Future就相当于一个适配器,
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
public class FutureTask<V> implements RunnableFuture<V> {
private Callable<V> callable;
/**
*构造方法
*/
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
/**
最终执行
*/
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();
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);
}
}
}
}
FutureTask 获取返回值 get()方法
由上面实现我们知道,在run方法中进行返回值result的初始化,但是run方法是多线程开启后回调的,是异步的,get方法是同步的,每次调用**get()**方法时候,多线程可能还没有执行完毕,FutureTask 通过自旋的方式,不断的查询(其实最多循环三次,就进入了阻塞等待唤醒)所开启的线程的状态,是否执行完毕,执行完毕才把result返回
这里用到了自旋 + 阻塞的方式,根据操作系统中对线程状态的定义,自旋的线程一直处于用户态,阻塞切换到内核态,需要上下文切换,性能损耗比较大,但是如果自旋的时间比较长,就会长时间占用cpu而不释放,浪费cpu资源
也就是说,如果一直使用循环,不断的判断线程的状态的话,循环次数多,那么长时间的自旋就会导致cpu资源浪费,而如果直接使用阻塞的方式等待唤醒,可能线程一开启就因为某些原因挂了,线程都没怎么执行,挂了后还要唤醒,上下文切换性能损耗相对就比较大了,所以这里使用了自旋 + 阻塞的方式
**awaitDone()方法内部有一个无限循环,看似有很多判断,比较难理解,其实这个循环最多循环3次。
假设Thread A执行了get()获取计算任务执行结果,但是子任务还没有执行完,而且Thread A没有被中断,它会进行以下步骤。
step1:Thread A执行了awaitDone(),1,2两次判断都不成立,Thread A判断q=null,会创建一个WaitNode节点q,然后进入第二次循环。
step2:第二次循环,判断4不成立,此时将step1创建的节点q加入队列头。
step3:第三次循环,判断是否设置了超时时间,如果设置了超时时间,就阻塞特定时间,否则,一直阻塞,等待被其他线程唤醒。
**
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
for (;;) {
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
else if (q == null)
q = new WaitNode();
else if (!queued)
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos); //阻塞
}
else
LockSupport.park(this); //阻塞
}
}
扩展——LockSupport
LockSupport 是一个阻塞工具类,提供了线程的阻塞和唤醒方法,所有方法都是静态的,有兴趣可以自己了解下…