线程基础内容
什么是线程?线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务
线程的生命周期
1、新建状态(new)
用new创建的线程,处于新建状态,和其他java对象一样,仅仅在堆中分配了内存。
2、就绪状态(Runnable)
创建线程后,调用start()方法,进入就绪状态,这时线程位于可运行池中,等待获得CPU权限。
3、运行状态(Running)
线程占用CPU,执行程序代码
4、阻塞状态(Blocked)
阻塞状态的线程,java虚拟机不会给线程分配CPU,直到线程进入就绪状态,才有机会获得CPU,转为运行状态。
阻塞状态分为三种情况:
- 位于对象等待池中:线程运行时,若执行了某个对象的wait()方法,java虚拟机就会把线程放到这个对象的等待池中。
- 位于对象锁的阻塞状态:线程运行时,试图获得某个对象的同步锁,但是该锁已经被其他线程占用,JVM就会把线程放到这个对象的锁池中。
- 其他阻塞状态:当前线程执行了sleep()方法,或者调用了其他线程的join()方法,或者发出I/O请求时,就会进入这个状态。
线程常用方法:
isAlive() 判断线程是否处于运行状态,运行时返回true,返回false时可能是等待状态,也可能是停止状态。
suspend() 挂起线程,可以通过:resume() 唤醒
sleep(long millis) 线程休眠一定时间,让其他线程有机会执行,但是并不会释放对象的锁,其他线程仍然不能获得该对象。例如两个线程a和b,a的优先级比b高,那么就要等a执行完,b才能执行,但是a.sleep(1000)后,b可以有执行的机会。a休眠结束后,不一定马上执行,而是进入就绪状态,等待系统调度。使用时:Thread.sleep(1000);
stop() 终止线程
interrupt() 中断线程,和stop区别是,stop强行停止,而interrupt会先依次关闭资源再停止。
join() 在当前线程main中,t线程调用join,表示t线程插队到mian线程之前,t执行完之后,在执行main,也就是使异步线程变为同步线程。若使用t.join(1000),带参数的是指,让t插队一定时间,时间过后main也会执行,若t没执行完,二者异步执行。
wait() 这个方法是Object类的拥有的,不是线程的方法。当在多线程执行的这个对象中,对象调用了wait,表示线程释放了对象的锁,JVM会把该线程放到对象的等待池中。现在该对象没有线程执行,所有线程均在等待池中等待。
wait(long timeout) 和wait类似,但是wait是一直等待,直到其他线程调用该对象的notify或notifyAll才会唤醒线程池中的线程。注意,调用wait(),notify()等对象的方法时,必须是获得对象锁的线程进行调用,没用获得锁的线程不能调用,否则会抛出java.lang.IllegalMonitorStateException。
wait(long timeout)在一定时间等待,并且会释放锁,直到其他线程调用该对象的notify或notifyAll,或者到了时间也会唤醒该线程,注意这里到时间后,仍然是该线程获取锁。
notify() 对象调用的方法,调用后,会依据优先级,从对象的线程等待池中唤醒一个,若优先级相同,则随机唤醒。
notifyAll() 对象调用方法,使等待池内所有的线程都唤醒,然后让所有线程竞争。
yield() 暂停当前线程,即让获得CPU执行权的线程,让出执行权,回到就绪状态,让所有线程竞争,不排除该线程马上再次获得执行权。
volatile变量 使用volatile修饰的变量,相当于线程同步的弱实现,所有的线程在操作该变量时,都会重新去主内存中取,而不能从各个线程的工作内存中取。
注:
- stop,suspend,resume可能会造成不可预料的结果,不推荐使用。
- wait(),notify(),notifyAll()是object类的方法,sleep(),yield()是Thread类的方法。
新建线程
在JDK5之前,可以使用继承Thread类和实现Runable接口来创建一个进程。而在JDK5之后,又新增了两种,实现Callable接口和使用线程池创建线程。
继承Thread类
继承Thread类虽然也是一种实现多线程的方式,但是Thread类本身也是实现了Runnable接口。
Thread类本身就是一个线程的实例。启动线程使用Thread类的start()方法,start()方法是一个native方法(native方法是指java调用非java代码的接口,方法的实现由非java语言实现,比如C或者C++),start()方法将启动新的线程并执行run()方法。
定义一个类,继承Thread类,然后重写run()方法:
class user extends Thread{
public void run(){
System.out.println("my thread");
}}
调用的时候,直接实例化,然后调用start()方法:
user u = new user();
u.start();
调用start()才会开启线程,直接执行方法不会开启
start方法源码:
class Thread implements Runnable { //Thread类实现了Runnalbe接口,实现了run()方法
private Runnable target;
public synchronized void start() {
...
boolean started = false;
try {
start0(); //可以看到,start()方法真实的调用时start0()方法
started = true;
} finally {
...
}
}
private native void start0(); //start0()是一个native方法,由JVM调用底层操作系统,开启一个线程,由操作系统过统一调度
@Override
public void run() {
if (target != null) {
target.run(); //操作系统在执行新开启的线程时,回调Runnable接口的run()方法,执行我们预设的线程任务
}
}}
注:JAVA不能直接创建线程执行任务,而是通过创建Thread对象调用操作系统开启线程,在由操作系统回调Runnable接口的run()方法执行任务;
实现Runnable接口
如果需要实现多线程的类已经继承了其他的类,由于java不支持多继承,所以就不能使用继承Thread类的方式,只能实现Rnnable接口,然后重写run()方法:
class user2 implements Runnable{
public void run() {
System.out.println("runnable");
}}
调用的时候,由于实现Runnable接口的类没有start方法,只有Thread类才有,Thread类有一个构造方法:
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
这个构造方法接受Runnable子类实例,即通过Thread类启动Runnable实现多线程。实例化需要多线程的类和一个Thread类,将自己的类传入Thread类中:
user2 u2 = new user2();
Thread t = new Thread(u2);
t.start();
注意:
- run()和start()方法区别
虽然调用start()方法也是启动线程,执行run方法,但是这是在新的线程中调用的,并且这是一个线程;如果直接调用run()方法,则run方法就是一个普通的方法,不会在新的线程中调用,也没有线程的任何特性。
普通的方法是直接执行完毕的,方法不执行完,不会返回并且下面的方法不会执行;而线程中的run方法是有一定的CPU时间的,执行过后,会切换到其他线程中,然后在切换回来,只是反复切换的速度很快,所以会感觉是多个线程同时执行一样。
- 继承Thread类和实现Runnable接口区别和联系:
区别:
两种方式都可以实现多线程,但是Runnable接口有以下优势:
- 避免java单继承的局限。
- 适用于资源共享:
如果使用继承Thread类实现的多线程,多个线程会得到多个不同的实例:
user u = new user();
user u1 = new user();
user u2 = new user();
u.start();
u1.start();
u2.start();
比如卖票系统,一共卖出10本书,三个线程会卖出30本书。
如果实现Runnable接口:
user2 u2 = new user2();
Thread t = new Thread(u2);
Thread t1 = new Thread(u2);
Thread t2 = new Thread(u2);
t.start();
t1.start();
t2.start();
这样得到的是一个实例,多个线程也只会卖10张票,达到了资源共享的目的。
联系:
- Thread类本身也是实现的Runnable接口。
Callable
Runnable是一种有很大局限的抽象,它不能返回一个值或抛出一个受检查的异常。Runnable接口
public interface Runnable {
public abstract void run();
}
由于run()方法返回值为void类型,所以在执行完任务之后无法返回任何结果。
许多任务实际上都是存在延迟的计算,对于这些任务,Callable是一种更好的抽象:它会返回一个值,并可能抛出一个异常。Callable接口:
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
可以看到,这是一个泛型接口,call()函数返回的类型就是传递进来的V类型。
Future
Future表示一个任务的生命周期,并提供了方法来判断是否已经完成或取消,以及获取任务的结果和取消任务等。Future接口:
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
在Future接口中声明了5个方法,下面依次解释每个方法的作用:
cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务。
isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
isDone方法表示任务是否已经完成,若任务完成,则返回true;
get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。
也就是说实际上Future提供了三种功能:
1判断任务是否完成;
2中断任务;
3获取任务执行结果。
FutureTask
Future只是一个接口,无法直接创建对象,因此有了FutureTask。 在提交任务时,用户实现的Callable实例task会被包装为FutureTask实例;提交后任务异步执行,无需用户关心;当用户需要时,再调用FutureTask#get()获取结果——或异常。
FutureTask原理:
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);
}
}
首先判断任务的状态,如果任务状态不是new,说明任务状态已经改变(说明他已经走了上面4种可能变化的一种,比如caller调用了cancel,此时状态为Interrupting, 也说明了上面的cancel方法,task没运行时,就interrupt, task得不到运行,总是返回)
如果状态是new, 判断runner是否为null, 如果为null, 则把当前执行任务的线程赋值给runner,如果runner不为null, 说明已经有线程在执行,返回。此处使用cas来赋值worker thread是保证多个线程同时提交同一个FutureTask时,确保该FutureTask的run只被调用一次, 如果想运行多次,使用runAndReset()方法。
这里
!UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())
语义相当于
if (this.runner == null ){
this.runner = Thread.currentThread();
}
使用compareAndSwap能够保证原子性。关于compareAndSwap的相关内容,可参看:http://huangyunbin.iteye.com/blog/1942369
接着开始执行任务,如果要执行的任务不为空,并且state为New就执行,可以看到这里调用了Callable的call方法。如果执行成功则set结果,如果出现异常则setException。最后把runner设为null。
接着看下set方法:
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
如果现在的状态是NEW就把状态设置成COMPLETING,然后设置成NORMAL。这个执行流程的状态变化就是: NEW->COMPLETING->NORMAL。
最后执行finishCompletion()方法:
private void finishCompletion() {
// assert state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) {
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
for (;;) {
Thread t = q.thread;
if (t != null) {
q.thread = null;
LockSupport.unpark(t);
}
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
done();
callable = null; // to reduce footprint
}
finishCompletion()会解除所有阻塞的worker thread, 调用done()方法,将成员变量callable设为null。这里使用了LockSupport类来解除线程阻塞,关于LockSupport,可参见:LockSupport的park和unpark的基本使用,以及对线程中断的响应性
接下来分析FutureTask非常重要的get方法:
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
首先判断FutureTask的状态是否为完成状态,如果是完成状态,说明已经执行过set或setException方法,返回report(s):
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
可以看到,如果FutureTask的状态是NORMAL, 即正确执行了set方法,get方法直接返回处理的结果, 如果是取消状态,即执行了setException,则抛出CancellationException异常。
如果get时,FutureTask的状态为未完成状态,则调用awaitDone方法进行阻塞。awaitDone():
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);
}
}
awaitDone方法可以看成是不断轮询查看FutureTask的状态。在get阻塞期间:
如果执行get的线程被中断,则移除FutureTask的所有阻塞队列中的线程(waiters),并抛出中断异常;
如果FutureTask的状态转换为完成状态(正常完成或取消),则返回完成状态;
如果FutureTask的状态变为COMPLETING, 则说明正在set结果,此时让线程等一等;
如果FutureTask的状态为初始态NEW,则将当前线程加入到FutureTask的阻塞线程中去;
如果get方法没有设置超时时间,则阻塞当前调用get线程;如果设置了超时时间,则判断是否达到超时时间,如果到达,则移除FutureTask的所有阻塞列队中的线程,并返回此时FutureTask的状态,如果未到达时间,则在剩下的时间内继续阻塞当前线程。
FutureTask源码分析
成员变量
- private volatile int state
private volatile int state
,state
用来标识当前任务的运行状态。FutureTask
的所有方法都是围绕这个状态进行的,需要注意,这个值用volatile
(易变的)来标记,如果有多个子线程在执行FutureTask,那么它们看到的都会是同一个state:
NEW
:表示这是一个新的任务,或者还没有执行完的任务,是初始状态。COMPLETING
:表示任务执行结束(正常执行结束,或者发生异常结束),但是还没有将结果保存到outcome
中。是一个中间状态。
NORMAL
:表示任务正常执行结束,并且已经把执行结果保存到outcome
字段中。是一个最终状态。EXCEPTIONAL
:表示任务发生异常结束,异常信息已经保存到outcome
中,这是一个最终状态。
CANCELLED
:任务在新建之后,执行结束之前被取消了,但是不要求中断正在执行的线程,也就是调用了cancel(false)
,任务就是CANCELLED
状态,这时任务状态变化是NEW -> CANCELLED。
INTERRUPTING
:任务在新建之后,执行结束之前被取消了,并要求中断线程的执行,也就是调用了cancel(true)
,这时任务状态就是INTERRUPTING
。这是一个中间状态。
INTERRUPTED
:调用cancel(true)
取消异步任务,会调用interrupt()
中断线程的执行,然后状态会从INTERRUPTING
变到INTERRUPTED
。
状态变化有如下4种情况:
NEW -> COMPLETING -> NORMAL --------------------------------------- 正常执行结束的流程
NEW -> COMPLETING -> EXCEPTIONAL ---------------------执行过程中出现异常的流程
NEW -> CANCELLED -------------------------------------------被取消,即调用了cancel(false)
NEW -> INTERRUPTING -> INTERRUPTED -------------被中断,即调用了cancel(true)
- private Callable<V> callable
private Callable<V> callable
,一个Callable
类型的变量,封装了计算任务,可获取计算结果。从上面的用法中可以看到,FutureTask
的构造函数中,我们传入的就是实现了Callable的接口的计算任务。
- private Object outcome
private Object outcome
,Object
类型的变量outcome
,用来保存计算任务的返回结果,或者执行过程中抛出的异常。
- private volatile Thread runner
private volatile Thread runner
,指向当前在运行Callable
任务的线程,runner
在FutureTask中的赋值变化很值得关注,后面源码会详细介绍这个。
- private volatile WaitNode waiters
private volatile WaitNode waiters
,WaitNode
是FutureTask的内部类,表示一个阻塞队列,如果任务还没有执行结束,那么调用get()获取结果的线程会阻塞,在这个阻塞队列中排队等待。
方法
- run()
FutureTask封装了计算任务,无论是提交给Thread执行,或者线程池执行,调用的都是FutureTask的run()。
public void run() {
//1.判断状态是否是NEW,不是NEW,说明任务已经被其他线程执行,甚至执行结束,或者被取消了,直接返回
//2.调用CAS方法,判断RUNNER为null的话,就将当前线程保存到RUNNER中,设置RUNNER失败,就直接返回
if (state != NEW ||
!U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
//3.执行Callable任务,结果保存到result中
result = c.call();
ran = true;
} catch (Throwable ex) {
//3.1 如果执行任务过程中发生异常,将调用setException()设置异常
result = null;
ran = false;
setException(ex);
}
//3.2 任务正常执行结束调用set(result)保存结果
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
//4. 任务执行结束,runner设置为null,表示当前没有线程在执行这个任务了
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
//5. 读取状态,判断是否在执行的过程中,被中断了,如果被中断,处理中断
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
- 首先,判断state的值是不是NEW,如果不是NEW,说明线程已经被执行了,可能已经执行结束,或者被取消了,直接返回。
- 这里其实是调用了Unsafe的CAS方法,读取并设置runner的值,将当前线程保存到runner中,表示当前正在执行任务的线程。可以看到,这里设置的其实是RUNNER,和前面介绍的Thread类型的runner变量不一样的,那为什么还说设置的是runner的值?RUNNER在FutureTask中定义如下:
private static final long RUNNER;
//RUNNER是一个long类型的变量,指向runner字段的偏移地址,相当于指针
RUNNER = U.objectFieldOffset
(FutureTask.class.getDeclaredField("runner"));
关于Unsafe的CAS方法,简单介绍一下,它提供了一种对runner进行原子操作的方法,原子操作,意味着,这个操作不会被打断。runner被volatile字段修饰,只能保证,当多个子线程在执行FutureTask的时候,它们读取到的runner的值是同一个,但是不能保证原子操作,所以很容易读到脏数据(举个例子:线程A准备对runner进行读和写操作,读取到runner的值为null,这是,cpu切换执行线程B,线程B读取到runner的值也是null,然后又切换到线程A执行,线程A对runner赋值thread-A,此时runner的值已经不再是null,线程B读取到的runner=null就是脏数据),用Unsafe的CAS方法,来对runner进行读写,就能保证原子操作。多个线程访问run()方法时,会在这里同步。
- 读取callable变量,执行call(),并获取执行结果。
如果执行call()的过程中发生异常,就调用setException()设置异常,setException()定义如下:
protected void setException(Throwable t) {
if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
outcome = t;
U.putOrderedInt(this, STATE, EXCEPTIONAL); // final state
finishCompletion();
}
}
//a. 调用Unsafe的CAS方法,state从NEW --> COMPLETING,这里的STATE和上面的RUNNER定义类似,指向state字段的偏移地址。
//b. 将异常信息保存到outcome字段,state变成EXCEPTIONAL。
//c. 调用finishCompletion()。
//NEW --> COMPLETING --> EXCEPTIONAL。
如果任务正常执行结束,就调用set(result)保存结果,定义如下:
protected void set(V v) {
if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
outcome = v;
U.putOrderedInt(this, STATE, NORMAL); // final state
finishCompletion();
}
}
//a. 和setException()类似,state从NEW --> COMPLETING。
//b. 将正常执行的结果result保存到outcome,state变成NORMAL。
//c. 调用finishCompletion()。NEW --> COMPLETING --> NORMAL。
- 任务执行结束,runner设置为null,表示当前没有线程在执行这个任务了。
- 读取state状态,判断是否在执行的过程中被中断了,如果被中断,处理中断,看一下这个中断处理:
private void handlePossibleCancellationInterrupt(int s) {
// It is possible for our interrupter to stall before getting a
// chance to interrupt us. Let's spin-wait patiently.
if (s == INTERRUPTING)
while (state == INTERRUPTING)
Thread.yield(); // wait out pending interrupt
}
如果状态是INTERRUPTING,表示正在被中断,这时就让出线程的执行权,给其他线程来执行。
- get()
一般情况下,执行任务的线程和获取结果的线程不会是同一个,当我们在主线程或者其他线程中,获取计算任务的结果时,就会调用get方法,如果这时计算任务还没有执行完成,调用get()的线程就会阻塞等待。get()实现如下:
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
- 读取任务的执行状态 state ,如果 state <= COMPLETING,说明线程还没有执行完(run()中可以看到,只有任务执行结束,或者发生异常的时候,state才会被设置成COMPLETING)。
- 调用awaitDone(false, 0L),进入阻塞状态。看一下awaitDone(false, 0L)的实现:
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
long startTime = 0L; // Special value 0L means not yet parked
WaitNode q = null;
boolean queued = false;
for (;;) {
//1. 读取状态
//1.1 如果s > COMPLETING,表示任务已经执行结束,或者发生异常结束了,就不会阻塞,直接返回
int s = state;
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
//1.2 如果s == COMPLETING,表示任务结束(正常/异常),但是结果还没有保存到outcome字段,当前线程让出执行权,给其他线程先执行
else if (s == COMPLETING)
// We may have already promised (via isDone) that we are done
// so never return empty-handed or throw InterruptedException
Thread.yield();
//2. 如果调用get()的线程被中断了,就从等待的线程栈中移除这个等待节点,然后抛出中断异常
else if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
//3. 如果等待节点q=null,就创建一个等待节点
else if (q == null) {
if (timed && nanos <= 0L)
return s;
q = new WaitNode();
}
//4. 如果这个等待节点还没有加入等待队列,就加入队列头
else if (!queued)
queued = U.compareAndSwapObject(this, WAITERS,
q.next = waiters, q);
//5. 如果设置了超时等待时间
else if (timed) {
//5.1 设置startTime,用于计算超时时间,如果超时时间到了,就等待队列中移除当前节点
final long parkNanos;
if (startTime == 0L) { // first time
startTime = System.nanoTime();
if (startTime == 0L)
startTime = 1L;
parkNanos = nanos;
} else {
long elapsed = System.nanoTime() - startTime;
if (elapsed >= nanos) {
removeWaiter(q);
return state;
}
parkNanos = nanos - elapsed;
}
// nanoTime may be slow; recheck before parking
//5.2 如果超时时间还没有到,而且任务还没有结束,就阻塞特定时间
if (state < COMPLETING)
LockSupport.parkNanos(this, parkNanos);
}
//6. 阻塞,等待唤醒
else
LockSupport.park(this);
}
}
这里主要有几个步骤:
a. 读取state,如果s > COMPLETING,表示任务已经执行结束,或者发生异常结束了,此时,调用get()的线程就不会阻塞;如果s == COMPLETING,表示任务结束(正常/异常),但是结果还没有保存到outcome字段,当前线程让出执行权,给其他线程先执行。
b. 判断Thread.interrupted(),如果调用get()的线程被中断了,就从等待的线程栈(其实就是一个WaitNode节点队列或者说是栈)中移除这个等待节点,然后抛出中断异常。
c. 判断q == null,如果等待节点q为null,就创建等待节点,这个节点后面会被插入阻塞队列。
d. 判断queued,这里是将c中创建节点q加入队列头。使用Unsafe的CAS方法,对waiters进行赋值,waiters也是一个WaitNode节点,相当于队列头,或者理解为队列的头指针。通过WaitNode可以遍历整个阻塞队列。
e. 之后,判断timed,这是从get()传入的值,表示是否设置了超时时间。设置超时时间之后,调用get()的线程最多阻塞nanos,就会从阻塞状态醒过来。如果没有设置超时时间,就直接进入阻塞状态,等待被其他线程唤醒。
awaitDone()方法内部有一个无限循环,看似有很多判断,比较难理解,其实这个循环最多循环3次。
假设Thread A执行了get()获取计算任务执行结果,但是子任务还没有执行完,而且Thread A没有被中断,它会进行以下步骤。
step1:Thread A执行了awaitDone(),1,2两次判断都不成立,Thread A判断q=null,会创建一个WaitNode节点q,然后进入第二次循环。
step2:第二次循环,判断4不成立,此时将step1创建的节点q加入队列头。
step3:第三次循环,判断是否设置了超时时间,如果设置了超时时间,就阻塞特定时间,否则,一直阻塞,等待被其他线程唤醒。
- 从awaitDone()返回,最后调用report(int s),这个后面再介绍。
- cancel(boolean mayInterruptIfRunning)
通常调用cancel()的线程和执行子任务的线程不会是同一个。当FutureTask的cancel(boolean mayInterruptIfRunning)方法被调用时,如果子任务还没有执行,那么这个任务就不会执行了,如果子任务已经执行,且mayInterruptIfRunning=true,那么执行子任务的线程会被中断(注意:这里说的是线程被中断,不是任务被取消),下面看一下这个方法的实现:
public boolean cancel(boolean mayInterruptIfRunning) {
//1.判断state是否为NEW,如果不是NEW,说明任务已经结束或者被取消了,该方法会执行返回false
//state=NEW时,判断mayInterruptIfRunning,如果mayInterruptIfRunning=true,说明要中断任务的执行,NEW->INTERRUPTING
//如果mayInterruptIfRunning=false,不需要中断,状态改为CANCELLED
if (!(state == NEW &&
U.compareAndSwapInt(this, STATE, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try { // in case call to interrupt throws exception
if (mayInterruptIfRunning) {
try {
//2.读取当前正在执行子任务的线程runner,调用t.interrupt(),中断线程执行
Thread t = runner;
if (t != null)
t.interrupt();
} finally { // final state
//3.修改状态为INTERRUPTED
U.putOrderedInt(this, STATE, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
cancel()分析:
- 判断state,保证state = NEW才能继续cancel()的后续操作。state=NEW且mayInterruptIfRunning=true,说明要中断任务的执行,此时,NEW->INTERRUPTING。然后读取当前执行任务的线程runner,调用t.interrupt(),中断线程执行,NEW->INTERRUPTING->INTERRUPTED,最后调用finishCompletion()。
- 如果NEW->INTERRUPTING,那么cancel()方法,只是修改了状态,NEW->CANCELLED,然后直接调用finishCompletion()。
所以cancel(true)方法,只是调用t.interrupt(),此时,如果t因为sleep(),wait()等方法进入阻塞状态,那么阻塞的地方会抛出InterruptedException;如果线程正常运行,需要结合Thread的interrupted()方法进行判断,才能结束,否则,cancel(true)不能结束正在执行的任务。
这也就可以解释前面我遇到的问题,有的情况下,使用 futuretask.cancel(true)方法并不能真正的结束子任务执行。
- finishCompletion()
前面多次出现过这个方法,set(V v)(保存执行结果,设置状态为NORMAL),setException(Throwable t)(保存结果,设置状态为EXCEPTIONAL)和cancel(boolean mayInterruptIfRunning)(设置状态为CANCELLED/INTERRUPTED),该方法在state变成最终态之后,会被调用。
private void finishCompletion() {
// assert state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) {
if (U.compareAndSwapObject(this, WAITERS, q, null)) {
for (;;) {
Thread t = q.thread;
if (t != null) {
q.thread = null;
LockSupport.unpark(t);
}
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
done();
callable = null; // to reduce footprint
}
finishCompletion()主要做了三件事情:
- 遍历waiters等待队列,调用LockSupport.unpark(t)唤醒等待返回结果的线程,释放资源。
- 调用done(),这个方法什么都没有做,不过子类可以实现这个方法,做一些额外的操作。
- 设置callable为null,callable是FutureTask封装的任务,任务执行完,释放资源。
这里可以解答上面的第二个问题了。FutureTask的get(long timeout, TimeUnit unit)方法,表示阻塞timeout时间后,获取子线程的执行结果,但是如果子任务执行结束了,但是超时时间还没有到,这个方法也会返回结果。因为任务执行完之后,会遍历阻塞队列,唤醒阻塞的线程。LockSupport.unpark(t)执行之后,阻塞的线程会从LockSupport.park(this)/LockSupport.parkNanos(this, parkNanos)醒来,然后会继续进入awaitDone(boolean timed, long nanos)的while循环,此时,state >= COMPLETING,然后从awaitDone()返回。此时,get()/get(long timeout, TimeUnit unit)会继续执行,return report(s),上面介绍get()的时候没介绍的方法。看一下report(int s):
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
其实就是读取outcome,将state映射到最后返回的结果中,s == NORMAL说明任务正常结束,返回正常结果,s >= CANCELLED,就抛出CancellationException。
- 其他
FutureTask的还有两个方法isCancelled()和isDone(),其实就是判断state,没有过多的步骤。
public boolean isCancelled() {
return state >= CANCELLED;
}
public boolean isDone() {
return state != NEW;
}
Future与FutureTask实例
- Future实例:
public class FutureTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
System.out.println("====进入主线程执行任务");
//通过线程池管理多线程
ExecutorService threadPool = Executors.newCachedThreadPool();
//线程池提交一个异步任务
System.out.println("====提交异步任务");
Future<HashMap<String,String>> future = threadPool.submit(new Callable<HashMap<String,String>>() {
@Override
public HashMap<String,String> call() throws Exception {
System.out.println("异步任务开始执行....");
Thread.sleep(2000);
System.out.println("异步任务执行完毕,返回执行结果!!!!");
return new HashMap<String,String>(){
{this.put("futureKey", "成功获取future异步任务结果");}
};
}
});
System.out.println("====提交异步任务之后,立马返回到主线程继续往下执行");
Thread.sleep(1000);
System.out.println("====此时需要获取上面异步任务的执行结果");
boolean flag = true;
while(flag){
//异步任务完成并且未被取消,则获取返回的结果
if(future.isDone() && !future.isCancelled()){
HashMap<String,String> futureResult = future.get();
System.out.println("====异步任务返回的结果是:"+futureResult.get("futureKey"));
flag = false;
}
}
//关闭线程池
if(!threadPool.isShutdown()){
threadPool.shutdown();
}
}
}
因为FutureTask 类是 Future 的一个实现,Future 可实现 Runnable,所以可通过 threadPool 来执行。例如,可用下列内容替换上面带有 submit 的构造(为简化,返回String类型):
FutureTask<String> future = new FutureTask<String>(new Callable<String>() {
public String call() {
return "成功获取future异步任务结果";
}
});
threadPool.execute(future);
- FutureTask实例1
public class FutureTaskSample {
static FutureTask<String> future = new FutureTask(new Callable<String>(){
public String call(){
return getPageContent();
}
});
public static void main(String[] args) throws InterruptedException, ExecutionException{
new Thread(future).start();
doOwnThing();
System.out.println(future.get());
}
public static String doOwnThing(){
return "Do Own Thing";
}
public static String getPageContent(){
return "testPageContent and provide that the operation is a time exhausted thing...";
}
}
- FutureTask实例2
@Test
public void FutureTask() throws ExecutionException, InterruptedException {
final UserApi userApi = new UserApi();
long startTime = System.currentTimeMillis();
Callable<String> userInfoCallable = new Callable<String>() {
public String call() throws Exception {
return userApi.getUserInfo();
}
};
Callable<String> addrInfoCallable = new Callable<String>() {
public String call() throws Exception {
return userApi.getAddrInfo();
}
};
FutureTask<String> userInfoFutureTask = new FutureTask<String>(userInfoCallable);
FutureTask<String> addrInfoFutureTask = new FutureTask<String>(addrInfoCallable);
new Thread(userInfoFutureTask).start();
new Thread(addrInfoFutureTask).start();
String userInfo = userInfoFutureTask.get();
String addrInfo = addrInfoFutureTask.get();
System.out.println("result:"+userInfo+addrInfo);
System.out.println(System.currentTimeMillis() - startTime);
}
守护线程
在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)
用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆:
只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
Daemon的作用是为其他线程的运行提供便利服务,守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务。守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。
User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。
守护线程并非只有虚拟机内部提供,用户在编写程序时也可以自己设置守护线程。下面的方法就是用来设置守护线程的。
Thread daemonTread = new Thread();
// 设定 daemonThread 为 守护线程,default false(非守护线程)
daemonThread.setDaemon(true);
// 验证当前线程是否为守护线程,返回 true 则为守护线程
daemonThread.isDaemon();
注意:
(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
(2) 在Daemon线程中产生的新线程也是Daemon的。
(3) 并不是所有的应用都可以分配给Daemon来进行服务,比如读写操作或者计算逻辑。因为你不可能知道在所有的User完成之前,Daemon是否已经完成了预期的服务任务。一旦User退出了,可能大量数据还没有来得及读入或写出,计算任务也可能多次运行结果不一样。这对程序是毁灭性的。造成这个结果理由已经说过了:一旦所有User Thread离开了,虚拟机也就退出运行了。
实例:
public class daemonTest {
public static void main(String[] args) {
Thread t1 = new MyCommon();
Thread t2 = new Thread(new MyDaemon());
t2.setDaemon(true); //设置为守护线程
t2.start();
t1.start();
}
}
class MyCommon extends Thread {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("线程1第" + i + "次执行!");
try {
Thread.sleep(7);
} catch (InterruptedException e) {
e.printStackTrace();
}}}}
class MyDaemon implements Runnable {
public void run() {
for (long i = 0; i < 9999999L; i++) {
System.out.println("后台线程第" + i + "次执行!");
try {
Thread.sleep(7);
} catch (InterruptedException e) {
e.printStackTrace();
}}}}
输出:
后台线程第0次执行!
线程1第0次执行!
后台线程第1次执行!
线程1第1次执行!
后台线程第2次执行!
线程1第2次执行!
后台线程第3次执行!
线程1第3次执行!
后台线程第4次执行!
线程1第4次执行!
后台线程第5次执行!
用户线程是保证执行完毕的,守护线程还没有执行完毕就退出了。
线程池
如果每一个请求都创建一个新线程,系统的开销很大,创建新线程和销毁新线程的时间和资源甚至比实际处理请求的时间和资源还要多,线程池主要是解决线程生命周期开销问题和资源不足问题。
线程池好处:
- 降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高相应速度,当任务到达时,任务可以不需要等待线程的创建就直接执行。
- 提高线程的可管理性,线程是稀缺资源,如果无限制的创建,会消耗大量资源。
线程池的设计
看下线程池中的属性,了解线程池的设计。
public class ThreadPoolExecutor extends AbstractExecutorService {
//线程池的打包控制状态,用高3位来表示线程池的运行状态,低29位来表示线程池中工作线程的数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//值为29,用来表示偏移量
private static final int COUNT_BITS = Integer.SIZE - 3;
//线程池的最大容量
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
//线程池的运行状态,总共有5个状态,用高3位来表示
private static final int RUNNING = -1 << COUNT_BITS; //接受新任务并处理阻
塞队列中的任务
private static final int SHUTDOWN = 0 << COUNT_BITS; //不接受新任务但会处
理阻塞队列中的任务
private static final int STOP = 1 << COUNT_BITS; //不会接受新任务,也
不会处理阻塞队列中的任务,并且中断正在运行的任务
private static final int TIDYING = 2 << COUNT_BITS; //所有任务都已终止,
工作线程数量为0,即将要执行terminated()钩子方法
private static final int TERMINATED = 3 << COUNT_BITS; //terminated()方
法已经执行结束
//任务缓存队列,用来存放等待执行的任务
private final BlockingQueue<Runnable> workQueue;
//全局锁,对线程池状态等属性修改时需要使用这个锁
private final ReentrantLock mainLock = new ReentrantLock();
//线程池中工作线程的集合,访问和修改需要持有全局锁
private final HashSet<Worker> workers = new HashSet<Worker>();
// 终止条件
private final Condition termination = mainLock.newCondition();
//线程池中曾经出现过的最大线程数
private int largestPoolSize;
//已完成任务的数量
private long completedTaskCount;
//线程工厂
private volatile ThreadFactory threadFactory;
//任务拒绝策略
private volatile RejectedExecutionHandler handler;
//线程存活时间
private volatile long keepAliveTime;
//是否允许核心线程超时
private volatile boolean allowCoreThreadTimeOut;
//核心池大小,若allowCoreThreadTimeOut被设置,核心线程全部空闲超时被回收的情况下会为0
private volatile int corePoolSize;
//最大池大小,不得超过CAPACITY
private volatile int maximumPoolSize;
//默认的任务拒绝策略
private static final RejectedExecutionHandler defaultHandler =new AbortPolicy();
//运行权限相关
private static final RuntimePermission shutdownPerm =new RuntimePermission("modifyThread");
...
}
小结:以上线程池的设计可以看出,线程池的功能还是很完善的。
1. 提供了线程创建、数量及存活时间等的管理;
2. 提供了线程池状态流转的管理;
3. 提供了任务缓存的各种容器;
4. 提供了多余任务的处理机制;
5. 提供了简单的统计功能
Executors类静态方法创建线程池
Executors提供的静态方法:
Executors.newCachedThreadPool();//创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE
Executors.newSingleThreadExecutor();//创建容量为1的缓冲池
Executors.newFixedThreadPool(int); //创建固定容量大小的缓冲池
下面是这三个静态方法的具体实现;
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
从它们的具体实现来看,它们实际上也是调用了ThreadPoolExecutor,只不过参数都已配置好了。
newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue;
newSingleThreadExecutor将corePoolSize和maximumPoolSize都设置为1,也使用的LinkedBlockingQueue;
newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors各个方法的弊端:
1)newFixedThreadPool和newSingleThreadExecutor:
主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
2)newCachedThreadPool和newScheduledThreadPool:
主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。
newSingleThreadExecutor
创建单线程线程池newSingleThreadExecutor
ExecutorService pool = Executors.newSingleThreadExecutor();
user u0 = new user();
user u1 = new user();
user u2 = new user();
pool.execute(u0);
pool.execute(u1);
pool.execute(u2);
单线程池只有一个线程在工作,也就相当于单线程串行执行所有任务,例如,有十个任务,每个任务耗时1秒,如果开10个线程去分别执行,则总共需要1秒,而单线程线程池只有一个线程工作,串行的去执行10个任务,需要10秒(执行完一个任务,再去执行另一个任务)。如果这个唯一的线程因为异常结束,则会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
A:pool-1-thread-1
A:pool-1-thread-1
A:pool-1-thread-1
newFixedThreadPool
固定大小的线程池 newFixedThreadPool
固定大小的线程池大小是设定的,每提交一个任务就创建一个线程,直到达到线程池最大值,到达最大值后,线程数不在增加,某个线程因异常结束,线程池会创建一个新的线程替代它。
ExecutorService pool = Executors.newFixedThreadPool(5);
user u0 = new user();
user u1 = new user();
user u2 = new user();
pool.execute(u2);
pool.execute(u1);
pool.execute(u0);
结果:
A:pool-1-thread-1
A:pool-1-thread-3
A:pool-1-thread-2
newCachedThreadPool
可缓存的线程池 newCachedThreadPool
可缓存的线程池,如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒内不执行任务)的线程,当任务数增加时,线程池又可以智能的增加新的线程,线程池大小没有限制,完全依赖操作系统(或JVM)能够创建的最大线程大小。
ExecutorService pool = Executors.newCachedThreadPool();
user u0 = new user();
user u1 = new user();
user u2 = new user();
pool.execute(u2);
pool.execute(u1);
pool.execute(u0);
结果:
A:pool-1-thread-2
A:pool-1-thread-1
A:pool-1-thread-3
ThreadPoolExecutor类构造方法创建
java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。
ThreadPoolExecutor类的具体实现源码。
ThreadPoolExecutor类四个构造方法
public class ThreadPoolExecutor extends AbstractExecutorService { ..... public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler); ... |
}
从上面的代码可以得知,ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。
下面解释下一下构造器中各个参数的含义:
各个参数的含义
- corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
- maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
- keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
- unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
- workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;
ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。
- threadFactory:线程工厂,主要用来创建线程;
- handler:表示当拒绝处理任务时的策略,有以下四种取值:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
具体参数的配置与线程池的关系将在下一节讲述。
public abstract class AbstractExecutorService implements ExecutorService {
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { };
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) { };
public Future<?> submit(Runnable task) {};
public <T> Future<T> submit(Runnable task, T result) { };
public <T> Future<T> submit(Callable<T> task) { };
private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
boolean timed, long nanos)
throws InterruptedException, ExecutionException, TimeoutException {
};
public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException {
};
public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
};
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
};
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException {
};
}
从上面给出的ThreadPoolExecutor类的代码可以知道,ThreadPoolExecutor继承了AbstractExecutorService,我们来看一下AbstractExecutorService的实现:
AbstractExecutorService是一个抽象类,它实现了ExecutorService接口。
我们接着看ExecutorService接口的实现:
public interface ExecutorService extends Executor {
void shutdown();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
而ExecutorService又是继承了Executor接口,我们看一下Executor接口的实现:
public interface Executor {
void execute(Runnable command);
}
到这里,大家应该明白了ThreadPoolExecutor、AbstractExecutorService、ExecutorService和Executor几个之间的关系了。
Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,从字面意思可以理解,就是用来执行传进去的任务的;
然后ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等;
抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法;
然后ThreadPoolExecutor继承了类AbstractExecutorService。
在ThreadPoolExecutor类中有几个非常重要的方法:
execute()
submit()
shutdown()
shutdownNow()
execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果(Future相关内容将在下一篇讲述)。
shutdown()和shutdownNow()是用来关闭线程池的。shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务,shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
setCorePoolSize:设置核心池大小,setMaximumPoolSize:设置线程池最大能创建的线程数目大小。
还有很多其他的方法:
比如:getQueue() 、getPoolSize() 、getActiveCount()、getCompletedTaskCount()等获取与线程池相关属性的方法,有兴趣的朋友可以自行查阅API。
ThreadPoolExecutor执行顺序
线程池按以下行为执行任务
当线程数小于核心线程数时,创建线程。
- 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
- 当线程数大于等于核心线程数,且任务队列已满
- 若线程数小于最大线程数,创建线程
- 若线程数等于最大线程数,抛出异常,拒绝任务
线程池执行步骤详解
- 提交任务方法
public void execute(Runnable command);
Future<?> submit(Runnable task);
Future submit(Runnable task, T result);
Future submit(Callable task);
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
可以看到submit方法的底层调用的也是execute方法,所以我们这里只分析execute方法;
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//第一步:创建核心线程
if (workerCountOf(c) < corePoolSize) { //worker数量小于corePoolSize
if (addWorker(command, true)) //创建worker
return;
c = ctl.get();
}
//第二步:加入缓存队列
if (isRunning(c) && workQueue.offer(command)) { //线程池处于RUNNING状态,将任务加入workQueue任务缓存队列
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command)) //双重检查,若线程池状态关闭了,移除任务
reject(command);
else if (workerCountOf(recheck) == 0) //线程池状态正常,但是没有线程了,创建worker
addWorker(null, false);
} /
/第三步:创建临时线程
else if (!addWorker(command, false))
reject(command);
}
小结:execute()方法主要功能:
1. 核心线程数量不足就创建核心线程;
2. 核心线程满了就加入缓存队列;
3. 缓存队列满了就增加非核心线程l;
4. 非核心线程也满了就拒绝任务;
- 创建线程
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {int c = ctl.get();int rs = runStateOf(c);
//等价于:rs>=SHUTDOWN && (rs != SHUTDOWN || firstTask != null ||workQueue.isEmpty())
//线程池已关闭,并且无需执行缓存队列中的任务,则不创建
if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))
return false;
for (;;) {int wc = workerCountOf(c);
if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c)) //CAS增加线程数
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner
loop
}
} /
/上面的流程走完,就可以真实开始创建线程了
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask); //这里创建了线程
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w); //这里将线程加入到线程池中
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start(); //添加成功,启动线程
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w); //添加线程失败操作
}
return workerStarted;
}
小结:addWorker()方法主要功能;
1. 增加线程数;
2. 创建线程Worker实例加入线程池;
3. 加入完成开启线程;
4. 启动失败则回滚增加流程;
- 工作线程的实现
private final class Worker //Worker类是ThreadPoolExecutor的内部类
extends AbstractQueuedSynchronizer
implements Runnable
{
final Thread thread; //持有实际线程
Runnable firstTask; //worker所对应的第一个任务,可能为空
volatile long completedTasks; //记录执行任务数
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this); //当前线程调用ThreadPoolExecutor中的runWorker方法,在这里实现的线程复用
} .
继承AQS,实现了不可重入锁...
}
小结:工作线程Worker类主要功能;
1. 此类持有一个工作线程,不断处理拿到的新任务,持有的线程即为可复用的线程;
2. 此类可看作一个适配类,在run()方法中真实调用runWorker()方法不断获取新任务,完成线程
复用;
- 线程的复用
final void runWorker(Worker w) { //ThreadPoolExecutor中的runWorker方法,在这里实现的线程复用
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true; //标识线程是否异常终止
try {
while (task != null || (task = getTask()) != null) { //这里会不断从任务队列获取任务并执行
w.lock();
//线程是否需要中断
if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task); //执行任务前的Hook方法,可自定义
Throwable thrown = null;
try {
task.run(); //执行实际的任务
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown); //执行任务后的Hook方法,可自定义
}
} finally {
task = null; //执行完成后,将当前线程中的任务制空,准备执行下一个任务
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly); //线程执行完成后的清理工作
}
}
小结:runWorker()方法主要功能;
1. 循环从缓存队列中获取新的任务,直到没有任务为止;
2. 使用worker持有的线程真实执行任务;
3. 任务都执行完成后的清理工作;
- 对列中获取待执行任务
private Runnable getTask() {
boolean timedOut = false; //标识当前线程是否超时未能获取到task对象
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c)) //若线程存活时间超时,则CAS减去线程数量
return null;
continue;
} t
ry {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
//允许超时回收则阻塞等待
workQueue.take(); //不允许则直接获取,没有就返回null
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
小结:getTask()方法主要功能;
1. 实际在缓存队列中获取待执行的任务;
2. 在这里管理线程是否要阻塞等待,控制线程的数量;
- 清理工作
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w); //移除执行完成的线程
} finally {
mainLock.unlock();
}
tryTerminate(); //每次回收完一个线程后都尝试终止线程池
int c = ctl.get();
if (runStateLessThan(c, STOP)) { //到这里说明线程池没有终止
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
addWorker(null, false); //异常终止线程的话,需要在常见一个线程
}
}
小结:processWorkerExit()方法主要功能;
1. 真实完成线程池线程的回收;
2. 调用尝试终止线程池;
3. 保证线程池正常运行;
- 尝试终止线程池
final void tryTerminate() {
for (;;) {
int c = ctl.get();
//若线程池正在执行、线程池已终止、线程池还需要执行缓存队列中的任务时,返回
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
//执行到这里,线程池为SHUTDOWN且无待执行任务 或 STOP 状态
if (workerCountOf(c) != 0) {
interruptIdleWorkers(ONLY_ONE); //只中断一个线程
return;
}
//执行到这里,线程池已经没有可用线程了,可以终止了
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) { //CAS设置线程池终止
try {
terminated(); //执行钩子方法
} finally {
ctl.set(ctlOf(TERMINATED, 0)); //这里将线程池设为终态
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
小结:tryTerminate()方法主要功能;
1. 实际尝试终止线程池;
2. 终止成功则调用钩子方法,并且将线程池置为终态。
如何设置参数
默认值
-
- corePoolSize=1
- queueCapacity=Integer.MAX_VALUE
- maxPoolSize=Integer.MAX_VALUE
- keepAliveTime=60s
- allowCoreThreadTimeout=false
- rejectedExecutionHandler=AbortPolicy()
- 如何来设置
- 需要根据几个值来决定
- tasks :每秒的任务数,假设为500~1000
- taskcost:每个任务花费时间,假设为0.1s
- responsetime:系统允许容忍的最大响应时间,假设为1s
- 做几个计算
- corePoolSize = 每秒需要多少个线程处理?
- threadcount = tasks/(1/taskcost) =tasks*taskcout = (500~1000)*0.1 = 50~100 个线程。corePoolSize设置应该大于50
- 根据8020原则,如果80%的每秒任务数小于800,那么corePoolSize设置为80即可
- queueCapacity = (coreSizePool/taskcost)*responsetime
- 计算可得 queueCapacity = 80/0.1*1 = 80。意思是队列里的线程可以等待1s,超过了的需要新开线程来执行
- 切记不能设置为Integer.MAX_VALUE,这样队列会很大,线程数只会保持在corePoolSize大小,当任务陡增时,不能新开线程来执行,响应时间会随之陡增。
- maxPoolSize = (max(tasks)- queueCapacity)/(1/taskcost)
- 计算可得 maxPoolSize = (1000-80)/10 = 92
- (最大任务数-队列容量)/每个线程每秒处理能力 = 最大线程数
- rejectedExecutionHandler:根据具体情况来决定,任务不重要可丢弃,任务重要则要利用一些缓冲机制来处理
- keepAliveTime和allowCoreThreadTimeout采用默认通常能满足
- corePoolSize = 每秒需要多少个线程处理?
- 需要根据几个值来决定
- 以上都是理想值,实际情况下要根据机器性能来决定。如果在未达到最大线程数的情况机器cpu load已经满了,则需要通过升级硬件(呵呵)和优化代码,降低taskcost来处理。
线程池大小设置
最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
解释:在线程执行的过程中,CPU时间越长,则CPU越密集,相对需要的线程数就较少;如果CPU时间越短,则在CPU计算方面使用的时间更短(或IO越密集),则需要更多的线程数。
线程池工作环境
1、IO密集型
2、CPU密集型
如果是CPU密集型的任务,应该设置数目较小的线程数。如果是IO密集型的任务,则应该设置可能多的线程数。
线程等待时间所占比例越高(线程此时很可能在等待依赖性任务完成或者处于IO阻塞,此时不占CPU,所以应该创建更多的线程让空闲的CPU去处理),需要越多线程。线程CPU时间所占比例越高,需要越少线程。
线程池实现原理
1.线程池状态
在ThreadPoolExecutor中定义了一个volatile变量,另外定义了几个static final变量表示线程池的各个状态:
volatile int runState;
static final int RUNNING = 0;
static final int SHUTDOWN = 1;
static final int STOP = 2;
static final int TERMINATED = 3;
runState表示当前线程池的状态,它是一个volatile变量用来保证线程之间的可见性;
下面的几个static final变量表示runState可能的几个取值。
当创建线程池后,初始时,线程池处于RUNNING状态;
如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。
2.任务的执行
在了解将任务提交给线程池到任务执行完毕整个过程之前,我们先来看一下ThreadPoolExecutor类中其他的一些比较重要成员变量:
private final BlockingQueue<Runnable> workQueue; //任务缓存队列,用来存放等待执行的任务
private final ReentrantLock mainLock = new ReentrantLock(); //线程池的主要状态锁,对线程池状态(比如线程池大小
//、runState等)的改变都要使用这个锁
private final HashSet<Worker> workers = new HashSet<Worker>(); //用来存放工作集
private volatile long keepAliveTime; //线程存货时间
private volatile boolean allowCoreThreadTimeOut; //是否允许为核心线程设置存活时间
private volatile int corePoolSize; //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)
private volatile int maximumPoolSize; //线程池最大能容忍的线程数
private volatile int poolSize; //线程池中当前的线程数
private volatile RejectedExecutionHandler handler; //任务拒绝策略
private volatile ThreadFactory threadFactory; //线程工厂,用来创建线程
private int largestPoolSize; //用来记录线程池中曾经出现过的最大线程数
private long completedTaskCount; //用来记录已经执行完毕的任务个数
在ThreadPoolExecutor类中,最核心的任务提交方法是execute()方法,虽然通过submit也可以提交任务,但是实际上submit方法里面最终调用的还是execute()方法:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
if (runState == RUNNING && workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
}
else if (!addIfUnderMaximumPoolSize(command))
reject(command); // is shutdown or saturated
}
}
if (runState == RUNNING && workQueue.offer(command))
如果当前线程池处于RUNNING状态,则将任务放入任务缓存队列;如果当前线程池不处于RUNNING状态或者任务放入缓存队列失败,则执行:
addIfUnderMaximumPoolSize(command)
如果说当前线程池处于RUNNING状态且将任务放入任务缓存队列成功,则继续进行判断:
if (runState != RUNNING || poolSize == 0)
这句判断是为了防止在将此任务添加进任务缓存队列的同时其他线程突然调用shutdown或者shutdownNow方法关闭了线程池的一种应急措施。如果是这样就执行:
ensureQueuedTaskHandled(command)
进行应急处理,从名字可以看出是保证 添加到任务缓存队列中的任务得到处理。
我们接着看2个关键方法的实现:addIfUnderCorePoolSize和addIfUnderMaximumPoolSize:
private boolean addIfUnderCorePoolSize(Runnable firstTask) {
Thread t = null;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (poolSize < corePoolSize && runState == RUNNING)
t = addThread(firstTask); //创建线程去执行firstTask任务
} finally {
mainLock.unlock();
}
if (t == null)
return false;
t.start();
return true;
}
这个是addIfUnderCorePoolSize方法的具体实现,从名字可以看出它的意图就是当低于核心池大小时执行的方法。下面看其具体实现,首先获取到锁,因为这地方涉及到线程池状态的变化,先通过if语句判断当前线程池中的线程数目是否小于核心池大小,有朋友也许会有疑问:前面在execute()方法中不是已经判断过了吗,只有线程池当前线程数目小于核心池大小才会执行addIfUnderCorePoolSize方法的,为何这地方还要继续判断?原因很简单,前面的判断过程中并没有加锁,因此可能在execute方法判断的时候poolSize小于corePoolSize,而判断完之后,在其他线程中又向线程池提交了任务,就可能导致poolSize不小于corePoolSize了,所以需要在这个地方继续判断。然后接着判断线程池的状态是否为RUNNING,原因也很简单,因为有可能在其他线程中调用了shutdown或者shutdownNow方法。然后就是执行
t = addThread(firstTask);
这个方法也非常关键,传进去的参数为提交的任务,返回值为Thread类型。然后接着在下面判断t是否为空,为空则表明创建线程失败(即poolSize>=corePoolSize或者runState不等于RUNNING),否则调用t.start()方法启动线程。
我们来看一下addThread方法的实现:
private Thread addThread(Runnable firstTask) {
Worker w = new Worker(firstTask);
Thread t = threadFactory.newThread(w); //创建一个线程,执行任务
if (t != null) {
w.thread = t; //将创建的线程的引用赋值为w的成员变量
workers.add(w);
int nt = ++poolSize; //当前线程数加1
if (nt > largestPoolSize)
largestPoolSize = nt;
}
return t;
}
在addThread方法中,首先用提交的任务创建了一个Worker对象,然后调用线程工厂threadFactory创建了一个新的线程t,然后将线程t的引用赋值给了Worker对象的成员变量thread,接着通过workers.add(w)将Worker对象添加到工作集当中。
下面我们看一下Worker类的实现:
private final class Worker implements Runnable {
private final ReentrantLock runLock = new ReentrantLock();
private Runnable firstTask;
volatile long completedTasks;
Thread thread;
Worker(Runnable firstTask) {
this.firstTask = firstTask;
}
boolean isActive() {
return runLock.isLocked();
}
void interruptIfIdle() {
final ReentrantLock runLock = this.runLock;
if (runLock.tryLock()) {
try {
if (thread != Thread.currentThread())
thread.interrupt();
} finally {
runLock.unlock();
}
}
}
void interruptNow() {
thread.interrupt();
}
private void runTask(Runnable task) {
final ReentrantLock runLock = this.runLock;
runLock.lock();
try {
if (runState < STOP &&
Thread.interrupted() &&
runState >= STOP)
boolean ran = false;
beforeExecute(thread, task); //beforeExecute方法是ThreadPoolExecutor类的一个方法,没有具体实现,用户可以根据
//自己需要重载这个方法和后面的afterExecute方法来进行一些统计信息,比如某个任务的执行时间等
try {
task.run();
ran = true;
afterExecute(task, null);
++completedTasks;
} catch (RuntimeException ex) {
if (!ran)
afterExecute(task, ex);
throw ex;
}
} finally {
runLock.unlock();
}
}
public void run() {
try {
Runnable task = firstTask;
firstTask = null;
while (task != null || (task = getTask()) != null) {
runTask(task);
task = null;
}
} finally {
workerDone(this); //当任务队列中没有任务时,进行清理工作
}
}
}
它实际上实现了Runnable接口,因此上面的Thread t = threadFactory.newThread(w);效果跟下面这句的效果基本一样:
Thread t = new Thread(w);
相当于传进去了一个Runnable任务,在线程t中执行这个Runnable。
既然Worker实现了Runnable接口,那么自然最核心的方法便是run()方法了:
public void run() {
try {
Runnable task = firstTask;
firstTask = null;
while (task != null || (task = getTask()) != null) {
runTask(task);
task = null;
}
} finally {
workerDone(this);
}
}
从run方法的实现可以看出,它首先执行的是通过构造器传进来的任务firstTask,在调用runTask()执行完firstTask之后,在while循环里面不断通过getTask()去取新的任务来执行,那么去哪里取呢?自然是从任务缓存队列里面去取,getTask是ThreadPoolExecutor类中的方法,并不是Worker类中的方法,下面是getTask方法的实现:
Runnable getTask() {
for (;;) {
try {
int state = runState;
if (state > SHUTDOWN)
return null;
Runnable r;
if (state == SHUTDOWN) // Help drain queue
r = workQueue.poll();
else if (poolSize > corePoolSize || allowCoreThreadTimeOut) //如果线程数大于核心池大小或者允许为核心池线程设置空闲时间,
//则通过poll取任务,若等待一定的时间取不到任务,则返回null
r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);
else
r = workQueue.take();
if (r != null)
return r;
if (workerCanExit()) { //如果没取到任务,即r为null,则判断当前的worker是否可以退出
if (runState >= SHUTDOWN) // Wake up others
interruptIdleWorkers(); //中断处于空闲状态的worker
return null;
}
// Else retry
} catch (InterruptedException ie) {
// On interruption, re-check runState
}
}
}
在getTask中,先判断当前线程池状态,如果runState大于SHUTDOWN(即为STOP或者TERMINATED),则直接返回null。
如果runState为SHUTDOWN或者RUNNING,则从任务缓存队列取任务。
如果当前线程池的线程数大于核心池大小corePoolSize或者允许为核心池中的线程设置空闲存活时间,则调用poll(time,timeUnit)来取任务,这个方法会等待一定的时间,如果取不到任务就返回null。
然后判断取到的任务r是否为null,为null则通过调用workerCanExit()方法来判断当前worker是否可以退出,我们看一下workerCanExit()的实现:
private boolean workerCanExit() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
boolean canExit;
//如果runState大于等于STOP,或者任务缓存队列为空了
//或者 允许为核心池线程设置空闲存活时间并且线程池中的线程数目大于1
try {
canExit = runState >= STOP ||
workQueue.isEmpty() ||
(allowCoreThreadTimeOut &&
poolSize > Math.max(1, corePoolSize));
} finally {
mainLock.unlock();
}
return canExit;
}
也就是说如果线程池处于STOP状态、或者任务队列已为空或者允许为核心池线程设置空闲存活时间并且线程数大于1时,允许worker退出。如果允许worker退出,则调用interruptIdleWorkers()中断处于空闲状态的worker,我们看一下interruptIdleWorkers()的实现:
void interruptIdleWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) //实际上调用的是worker的interruptIfIdle()方法
w.interruptIfIdle();
} finally {
mainLock.unlock();
}
}
从实现可以看出,它实际上调用的是worker的interruptIfIdle()方法,在worker的interruptIfIdle()方法中:
void interruptIfIdle() {
final ReentrantLock runLock = this.runLock;
if (runLock.tryLock()) { //注意这里,是调用tryLock()来获取锁的,因为如果当前worker正在执行任务,锁已经被获取了,是无法获取到锁的
//如果成功获取了锁,说明当前worker处于空闲状态
try {
if (thread != Thread.currentThread())
thread.interrupt();
} finally {
runLock.unlock();
}
}
}
这里有一个非常巧妙的设计方式,假如我们来设计线程池,可能会有一个任务分派线程,当发现有线程空闲时,就从任务缓存队列中取一个任务交给空闲线程执行。但是在这里,并没有采用这样的方式,因为这样会要额外地对任务分派线程进行管理,无形地会增加难度和复杂度,这里直接让执行完任务的线程去任务缓存队列里面取任务来执行。
我们再看addIfUnderMaximumPoolSize方法的实现,这个方法的实现思想和addIfUnderCorePoolSize方法的实现思想非常相似,唯一的区别在于addIfUnderMaximumPoolSize方法是在线程池中的线程数达到了核心池大小并且往任务队列中添加任务失败的情况下执行的:
private boolean addIfUnderMaximumPoolSize(Runnable firstTask) {
Thread t = null;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (poolSize < maximumPoolSize && runState == RUNNING)
t = addThread(firstTask);
} finally {
mainLock.unlock();
}
if (t == null)
return false;
t.start();
return true;
}
看到没有,其实它和addIfUnderCorePoolSize方法的实现基本一模一样,只是if语句判断条件中的poolSize < maximumPoolSize不同而已。
到这里,大部分朋友应该对任务提交给线程池之后到被执行的整个过程有了一个基本的了解,下面总结一下:
1)首先,要清楚corePoolSize和maximumPoolSize的含义;
2)其次,要知道Worker是用来起到什么作用的;
3)要知道任务提交给线程池之后的处理策略,这里总结一下主要有4点:
- 如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;
- 如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;
- 如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
- 如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。
3.线程池中的线程初始化
默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:
- prestartCoreThread():初始化一个核心线程;
- prestartAllCoreThreads():初始化所有核心线程
下面是这2个方法的实现:
public boolean prestartCoreThread() {
return addIfUnderCorePoolSize(null); //注意传进去的参数是null
}
public int prestartAllCoreThreads() {
int n = 0;
while (addIfUnderCorePoolSize(null))//注意传进去的参数是null
++n;
return n;
}
注意上面传进去的参数是null,根据第2小节的分析可知如果传进去的参数为null,则最后执行线程会阻塞在getTask方法中的
1 | r = workQueue.take(); |
即等待任务队列中有任务。
4.任务缓存队列及排队策略
在前面我们多次提到了任务缓存队列,即workQueue,它用来存放等待执行的任务。
workQueue的类型为BlockingQueue<Runnable>,通常可以取下面三种类型:
1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
2)LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
3)synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
5.任务拒绝策略
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
6.线程池的关闭
- shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
- shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
7.线程池容量的动态调整
- setCorePoolSize:设置核心池大小
- setMaximumPoolSize:设置线程池最大能创建的线程数目大小
使用实例
前面我们讨论了关于线程池的实现原理,这一节我们来看一下它的具体使用:
public class Test {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5));
for(int i=0;i<15;i++){
MyTask myTask = new MyTask(i);
executor.execute(myTask);
System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+executor.getQueue().size()+",已执行玩别的任务数目"+executor.getCompletedTaskCount());
}
executor.shutdown();
}
}
class MyTask implements Runnable {
private int taskNum;
public MyTask(int num) {
this.taskNum = num;
}
@Override
public void run() {
System.out.println("正在执行task "+taskNum);
try {
Thread.currentThread().sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task "+taskNum+"执行完毕");
}
}
拓展
线程顺序执行的方式
join方法
线程join
join():是Theard的方法,作用是调用线程需等待该join()线程执行完成后,才能继续用下运行。
应用场景:当一个线程必须等待另一个线程执行完毕才能执行时可以使用join方法。
public class ThreadJoinDemo {
public static void main(String[] args) {
final Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("产品经理规划新需求");
}
});
final Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
thread1.join();
System.out.println("开发人员开发新需求功能");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
try {
thread2.join();
System.out.println("测试人员测试新功能");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println("早上:");
System.out.println("测试人员来上班了...");
thread3.start();
System.out.println("产品经理来上班了...");
thread1.start();
System.out.println("开发人员来上班了...");
thread2.start();
}
}
运行结果:
早上:
测试人员来上班了…
产品经理来上班了…
开发人员来上班了…
产品经理规划新需求
开发人员开发新需求功能
测试人员测试新功能
主线程join
这里是在主线程中使用join()来实现对线程的阻塞。
public class ThreadMainJoinDemo {
public static void main(String[] args) throws Exception {
final Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("产品经理正在规划新需求...");
}
});
final Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("开发人员开发新需求功能");
}
});
final Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("测试人员测试新功能");
}
});
System.out.println("早上:");
System.out.println("产品经理来上班了");
System.out.println("测试人员来上班了");
System.out.println("开发人员来上班了");
thread1.start();
//在父进程调用子进程的join()方法后,父进程需要等待子进程运行完再继续运行。
System.out.println("开发人员和测试人员休息会...");
thread1.join();
System.out.println("产品经理新需求规划完成!");
thread2.start();
System.out.println("测试人员休息会...");
thread2.join();
thread3.start();
}
}
运行结果:
产品经理来上班了
测试人员来上班了
开发人员来上班了
开发人员和测试人员休息会…
产品经理正在规划新需求…
产品经理新需求规划完成!
测试人员休息会…
开发人员开发新需求功能
测试人员测试新功能
线程的wait
wait():是Object的方法,作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)
notify()和notifyAll():是Object的方法,作用则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。
wait(long timeout):让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的notify()方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
应用场景:Java实现生产者消费者的方式。
package com.wwj.javabase.thread.order;
/**
* @author wwj
*/
public class ThreadWaitDemo {
private static Object myLock1 = new Object();
private static Object myLock2 = new Object();
/**
* 为什么要加这两个标识状态?
* 如果没有状态标识,当t1已经运行完了t2才运行,t2在等待t1唤醒导致t2永远处于等待状态
*/
private static Boolean t1Run = false;
private static Boolean t2Run = false;
public static void main(String[] args) {
final Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (myLock1){
System.out.println("产品经理规划新需求...");
t1Run = true;
myLock1.notify();
}
}
});
final Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (myLock1){
try {
if(!t1Run){
System.out.println("开发人员先休息会...");
myLock1.wait();
}
synchronized (myLock2){
System.out.println("开发人员开发新需求功能");
myLock2.notify();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (myLock2){
try {
if(!t2Run){
System.out.println("测试人员先休息会...");
myLock2.wait();
}
System.out.println("测试人员测试新功能");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
System.out.println("早上:");
System.out.println("测试人员来上班了...");
thread3.start();
System.out.println("产品经理来上班了...");
thread1.start();
System.out.println("开发人员来上班了...");
thread2.start();
}
}
运行结果:这里输出会有很多种顺序,主要是因为线程进入的顺序,造成锁住线程的顺序不一致。
早上:
测试人员来上班了…
产品经理来上班了…
开发人员来上班了…
测试人员先休息会…
产品经理规划新需求…
开发人员开发新需求功能
测试人员测试新功能
线程池
JAVA通过Executors提供了四种线程池
单线程化线程池(newSingleThreadExecutor);
可控最大并发数线程池(newFixedThreadPool);
可回收缓存线程池(newCachedThreadPool);
支持定时与周期性任务的线程池(newScheduledThreadPool)。
单线程化线程池(newSingleThreadExecutor):优点,串行执行所有任务。
submit():提交任务。
shutdown():方法用来关闭线程池,拒绝新任务。
应用场景:串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
public class ThreadPoolDemo {
static ExecutorService executorService = Executors.newSingleThreadExecutor();
public static void main(String[] args) throws Exception {
final Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("产品经理规划新需求");
}
});
final Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("开发人员开发新需求功能");
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("测试人员测试新功能");
}
});
System.out.println("早上:");
System.out.println("产品经理来上班了");
System.out.println("测试人员来上班了");
System.out.println("开发人员来上班了");
System.out.println("领导吩咐:");
System.out.println("首先,产品经理规划新需求...");
executorService.submit(thread1);
System.out.println("然后,开发人员开发新需求功能...");
executorService.submit(thread2);
System.out.println("最后,测试人员测试新功能...");
executorService.submit(thread3);
executorService.shutdown();
}
}
运行结果
早上:
产品经理来上班了
测试人员来上班了
开发人员来上班了
领导吩咐:
首先,产品经理规划新需求…
然后,开发人员开发新需求功能…
最后,测试人员测试新功能…
产品经理规划新需求
开发人员开发新需求功能
测试人员测试新功能
Condition(条件变量)
Condition(条件变量):通常与一个锁关联。需要在多个Contidion中共享一个锁时,可以传递一个Lock/RLock实例给构造方法,否则它将自己生成一个RLock实例。
Condition中await()方法类似于Object类中的wait()方法。
Condition中await(long time,TimeUnit unit)方法类似于Object类中的wait(long time)方法。
Condition中signal()方法类似于Object类中的notify()方法。
Condition中signalAll()方法类似于Object类中的notifyAll()方法。
应用场景:Condition是一个多线程间协调通信的工具类,使得某个,或者某些线程一起等待某个条件(Condition),只有当该条件具备( signal 或者 signalAll方法被调用)时 ,这些等待线程才会被唤醒,从而重新争夺锁。
package com.wwj.javabase.thread.order;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author wwj
* 使用Condition(条件变量)实现线程按顺序运行
*/
public class ThreadConditionDemo {
private static Lock lock = new ReentrantLock();
private static Condition condition1 = lock.newCondition();
private static Condition condition2 = lock.newCondition();
/**
* 为什么要加这两个标识状态?
* 如果没有状态标识,当t1已经运行完了t2才运行,t2在等待t1唤醒导致t2永远处于等待状态
*/
private static Boolean t1Run = false;
private static Boolean t2Run = false;
public static void main(String[] args) {
final Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
System.out.println("产品经理规划新需求");
t1Run = true;
condition1.signal();
lock.unlock();
}
});
final Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
if(!t1Run){
System.out.println("开发人员先休息会...");
condition1.await();
}
System.out.println("开发人员开发新需求功能");
t2Run = true;
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
if(!t2Run){
System.out.println("测试人员先休息会...");
condition2.await();
}
System.out.println("测试人员测试新功能");
lock.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println("早上:");
System.out.println("测试人员来上班了...");
thread3.start();
System.out.println("产品经理来上班了...");
thread1.start();
System.out.println("开发人员来上班了...");
thread2.start();
}
}
运行结果:这里输出会有很多种顺序,主要是因为线程进入的顺序,造成锁住线程的顺序不一致
早上:
测试人员来上班了…
产品经理来上班了…
开发人员来上班了…
测试人员先休息会…
产品经理规划新需求
开发人员开发新需求功能
测试人员测试新功能
CountDownLatch
CountDownLatch:位于java.util.concurrent包下,利用它可以实现类似计数器的功能。
应用场景:比如有一个任务C,它要等待其他任务A,B执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。
package com.wwj.javabase.thread.order;
import java.util.concurrent.CountDownLatch;
/**
* @author wwj
* 通过CountDownLatch(倒计数)使线程按顺序执行
*/
public class ThreadCountDownLatchDemo {
/**
* 用于判断线程一是否执行,倒计时设置为1,执行后减1
*/
private static CountDownLatch c1 = new CountDownLatch(1);
/**
* 用于判断线程二是否执行,倒计时设置为1,执行后减1
*/
private static CountDownLatch c2 = new CountDownLatch(1);
public static void main(String[] args) {
final Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("产品经理规划新需求");
//对c1倒计时-1
c1.countDown();
}
});
final Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
//等待c1倒计时,计时为0则往下运行
c1.await();
System.out.println("开发人员开发新需求功能");
//对c2倒计时-1
c2.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
try {
//等待c2倒计时,计时为0则往下运行
c2.await();
System.out.println("测试人员测试新功能");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println("早上:");
System.out.println("测试人员来上班了...");
thread3.start();
System.out.println("产品经理来上班了...");
thread1.start();
System.out.println("开发人员来上班了...");
thread2.start();
}
}
运行结果
早上:
测试人员来上班了…
产品经理来上班了…
开发人员来上班了…
产品经理规划新需求
开发人员开发新需求功能
测试人员测试新功能
CyclicBarrier(回环栅栏)
CyclicBarrier(回环栅栏):通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。我们暂且把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了。
应用场景:公司组织春游,等待所有的员工到达集合地点才能出发,每个人到达后进入barrier状态。都到达后,唤起大家一起出发去旅行。
package com.wwj.javabase.thread.order;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* @author wwj
* 使用CyclicBarrier(回环栅栏)实现线程按顺序运行
*/
public class CyclicBarrierDemo {
static CyclicBarrier barrier1 = new CyclicBarrier(2);
static CyclicBarrier barrier2 = new CyclicBarrier(2);
public static void main(String[] args) {
final Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("产品经理规划新需求");
//放开栅栏1
barrier1.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
});
final Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
//放开栅栏1
barrier1.await();
System.out.println("开发人员开发新需求功能");
//放开栅栏2
barrier2.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
});
final Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
try {
//放开栅栏2
barrier2.await();
System.out.println("测试人员测试新功能");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
});
System.out.println("早上:");
System.out.println("测试人员来上班了...");
thread3.start();
System.out.println("产品经理来上班了...");
thread1.start();
System.out.println("开发人员来上班了...");
thread2.start();
}
}
运行结果
早上:
测试人员来上班了…
产品经理来上班了…
开发人员来上班了…
产品经理规划新需求
开发人员开发新需求功能
测试人员测试新功能
Semaphore
Sephmore(信号量):Semaphore是一个计数信号量,从概念上将,Semaphore包含一组许可证,如果有需要的话,每个acquire()方法都会阻塞,直到获取一个可用的许可证,每个release()方法都会释放持有许可证的线程,并且归还Semaphore一个可用的许可证。然而,实际上并没有真实的许可证对象供线程使用,Semaphore只是对可用的数量进行管理维护。
acquire():当前线程尝试去阻塞的获取1个许可证,此过程是阻塞的,当前线程获取了1个可用的许可证,则会停止等待,继续执行。
release():当前线程释放1个可用的许可证。
应用场景:Semaphore可以用来做流量分流,特别是对公共资源有限的场景,比如数据库连接。假设有这个的需求,读取几万个文件的数据到数据库中,由于文件读取是IO密集型任务,可以启动几十个线程并发读取,但是数据库连接数只有10个,这时就必须控制最多只有10个线程能够拿到数据库连接进行操作。这个时候,就可以使用Semaphore做流量控制。
package com.wwj.javabase.thread.order;
import java.util.concurrent.Semaphore;
/**
* @author wwj
* 使用Sephmore(信号量)实现线程按顺序运行
*/
public class SemaphoreDemo {
private static Semaphore semaphore1 = new Semaphore(1);
private static Semaphore semaphore2 = new Semaphore(1);
public static void main(String[] args) {
final Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("产品经理规划新需求");
semaphore1.release();
}
});
final Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
semaphore1.acquire();
System.out.println("开发人员开发新需求功能");
semaphore2.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
try {
semaphore2.acquire();
thread2.join();
semaphore2.release();
System.out.println("测试人员测试新功能");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println("早上:");
System.out.println("测试人员来上班了...");
thread3.start();
System.out.println("产品经理来上班了...");
thread1.start();
System.out.println("开发人员来上班了...");
thread2.start();
}
}
运行结果
早上:
测试人员来上班了…
产品经理来上班了…
开发人员来上班了…
产品经理规划新需求
开发人员开发新需求功能
测试人员测试新功能