JDK并发包
同步控制
重入锁
重入锁可以完全替代关键字synchronized。从JDK6.0开始,两者的性能差距才缩小。重入锁使用java.util.concurrent.locks.ReentrantLock类来实现。
简单使用
重入锁需要显式加锁,指定何时加锁,何时释放锁,灵活性优于关键字synchronized。
public class ReenterLock implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
public static int i = 0;
@Override
public void run() {
for (int j = 0; j < 10000000; j++) {
lock.lock();
try {
i++;
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
ReenterLock tl = new ReenterLock();
Thread t1 = new Thread(tl);
Thread t2 = new Thread(tl);
t1.start();
t2.start();
try{
t1.join();
t2.join();
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(i);
}
}
output:
20000000
重入锁的名字翻译自Re-Entrant-Lock,指单个线程可以反复进入,但加锁和解锁必须成对出现。
中断响应
对于synchronized关键字,某个线程在等待锁时只有两种情况:
- 获得锁继续执行。
- 保持等待。
而重入锁可以被中断:某个线程在等待锁的过程中,接到一个通知,告知其无须再等待,可以停止工作。这对解决死锁问题有帮助。例如下述代码解决死锁。
public class IntLock implements Runnable {
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
/**
* 用来控制加锁顺序,方便构造死锁
*/
int lock;
public IntLock(int lock){
this.lock = lock;
}
@Override
public void run(){
try{
if (lock == 1){
lock1.lockInterruptibly();
try{
Thread.sleep(500);
}catch (InterruptedException e){}
lock2.lockInterruptibly();
}else {
lock2.lockInterruptibly();
try{
Thread.sleep(500);
}catch (InterruptedException e){}
lock1.lockInterruptibly();
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
if (lock1.isHeldByCurrentThread()){
lock1.unlock();
}
if (lock2.isHeldByCurrentThread()){
lock2.unlock();
}
System.out.println(Thread.currentThread().getId()+":线程退出");
}
}
public static void main(String[] args) throws InterruptedException {
IntLock r1 = new IntLock(1);
IntLock r2 = new IntLock(2);
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
Thread.sleep(1000);
t2.interrupt();
}
}
output:
13:线程退出
java.lang.InterruptedException
12:线程退出
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at Concurrent.JDK.test2.IntLock.run(IntLock.java:38)
at java.lang.Thread.run(Thread.java:745)
锁申请限时等待
在无法判断线程发生死锁还是饥饿的情况下,使用tryLock方法进行限时等待。
public class TimeLock implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
try{
if (lock.tryLock(5, TimeUnit.SECONDS )){
System.out.println("Thread-"+Thread.currentThread().getId()+" get lock");
Thread.sleep(6000);
}else {
System.out.println("Thread-"+Thread.currentThread().getId()+" get lock failed");
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
if (lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
public static void main(String[] args) {
TimeLock tl = new TimeLock();
Thread t1 = new Thread(tl);
Thread t2 = new Thread(tl);
t1.start();
t2.start();
}
}
output:
Thread-12 get lock
Thread-13 get lock failed
ReentrantLock.tryLock() 方法可以不带参数直接调用。此时当前线程会尝试获取锁:
- 未被其他线程占用,获得锁,返回true。
- 已被其他线程占用,不等待,返回false。
public class TryLock implements Runnable {
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
int lock;
public TryLock(int lock) {
this.lock = lock;
}
@Override
public void run() {
if (lock == 1) {
while (true) {
if (lock1.tryLock()) {
try {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
if (lock2.tryLock()){
try{
System.out.println("Thread-"+Thread.currentThread().getId()+":my job done");
return;
}finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
}
}else {
while (true) {
if (lock2.tryLock()) {
try {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
if (lock1.tryLock()){
try{
System.out.println("Thread-"+Thread.currentThread().getId()+":my job done");
return;
}finally {
lock1.unlock();
}
}
} finally {
lock2.unlock();
}
}
}
}
}
public static void main(String[] args) throws InterruptedException {
TryLock r1 = new TryLock(1);
TryLock r2 = new TryLock(2);
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
long startTime = System.currentTimeMillis();
t1.start();
t2.start();
t1.join();
t2.join();
long endTime=System.currentTimeMillis();
System.out.println((endTime-startTime)/1000+"s");
}
}
output:
Thread-12:my job done
Thread-13:my job done
61s
两个线程在耗时61秒后完成任务。
公平锁
在大多数情况下,锁的申请都是非公平的。非公平锁可能会产生饥饿问题。公平锁按照时间的先后顺序,保证先来先得,后来后得。synchronized关键字进行锁控制是非公平的。重入锁允许进行公平性设置。
public ReentrantLock(boolean fair) // true表示锁是公平的
public class FairLock implements Runnable {
public static ReentrantLock fairLock = new ReentrantLock(true);
@Override
public void run() {
while (true) {
try {
fairLock.lock();
System.out.println(Thread.currentThread().getName() + " 获得锁");
} finally {
fairLock.unlock();
}
}
}
public static void main(String[] args) {
FairLock rl = new FairLock();
Thread t1 = new Thread(rl, "Thread_t1");
Thread t2 = new Thread(rl, "Thread_t2");
t1.start();
t2.start();
}
}
output:
Thread_t1 获得锁
Thread_t2 获得锁
Thread_t1 获得锁
Thread_t2 获得锁
Thread_t1 获得锁
Thread_t2 获得锁
...
若改为false,由于系统的调度,一个线程会倾向于再次获得已经持有的锁,高效,但不公平。
output:
Thread_t2 获得锁
Thread_t2 获得锁
Thread_t2 获得锁
Thread_t2 获得锁
Thread_t2 获得锁
Thread_t2 获得锁
Thread_t2 获得锁
Thread_t2 获得锁
Thread_t2 获得锁
Thread_t2 获得锁
Thread_t2 获得锁
...
重入锁的实现要素
重入锁的实现包含三个要素:
- 原子状态。原子状态使用CAS操作来存储当前所得状态,判断锁是否已被其他线程持有。
- 等待队列。所有没有请求道锁的线程,会进入等待队列进行等待。等到有线程释放锁之后,系统从等待队列中唤醒一个线程,继续工作。
- 阻塞原语park() 和unpark() ,用来挂起和恢复线程。没有得到所得线程将会被挂起。
Condition
Condition的用法类似于Object.wait() 方法和Object.notify() 方法。利用Condition对象,可以使线程在何时的时间等待,在某一个特定的时间得到通知,继续执行。通过Lock接口(重入锁实现了Lock接口)的Condition newCondition() 方法可以获得与当前重入锁绑定的Condition实例。
public interface Condition {
void await() throws InterruptedException;
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
void signal();
void signalAll();
}
await() 方法使当前线程等待,同时释放当前锁。其他线程使用signal() 方法或者signalAll() 方法时,线程会重新获得锁并继续执行。或者当前线程被中断,也能跳出等待。
public class ReenterLockCondition implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
public static Condition condition = lock.newCondition();
@Override
public void run() {
try {
lock.lock();
condition.await();
System.out.println("Thread is going on");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ReenterLockCondition tl = new ReenterLockCondition();
Thread t1 = new Thread(tl);
t1.start();
Thread.sleep(2000);
// 通知t1继续执行
lock.lock();
condition.signal();
lock.unlock();
}
}
信号量(Semaphore)
信号量为多线程协作提供了更强大的控制方法。广义上说,信号量是对锁的扩展。synchronized和ReentrantLock一次都只允许一个线程访问一个资源,而信号量可以指定多个线程同时访问某一个资源。
public Semaphore(int permits) // 指定信号量的准入数
public Semaphore(int permits, boolean fair) // 同时指定是否公平
public void acquire() throws InterruptedException // 获得一个准入许可
public void acquireUninterruptibly()
public boolean tryAcquire()
public boolean tryAcquire(long timeout, TimeUnit unit)
public void release()
在下面这个例子中,系统以5个先成为一组,依次输出提示文本。
public class SemaDemo implements Runnable {
final Semaphore semp = new Semaphore(5);
@Override
public void run() {
try {
semp.acquire();
Thread.sleep(2000);
System.out.println("Thread-" + Thread.currentThread().getId() + ":done!");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semp.release();
}
}
public static void main(String[] args) {
ExecutorService exec = Executors.newFixedThreadPool(20);
final SemaDemo demo = new SemaDemo();
for (int i = 0; i < 20; i++) {
exec.submit(demo);
}
}
}
读写锁
ReadWriteLock是JDK5中提供的读写分离锁,读写分离锁可以有效减少锁的竞争,提升系统性能。读写锁的访问约束情况如下。
线程 | 读 | 写 |
---|---|---|
读 | 不冲突 | 冲突 |
写 | 冲突 | 冲突 |
在Java多线程设计模式——读写锁分离中,对读写锁设计模式进行了讨论。
下面是读写锁使用的例子,该例子中使用Lock的用时要高于ReadWriteLock。
public class ReadWriteLockDemo {
private static Lock lock = new ReentrantLock();
private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static Lock readLock = readWriteLock.readLock();
private static Lock writeLock = readWriteLock.writeLock();
private int value;
public Object handleRead(Lock lock) throws InterruptedException {
try {
lock.lock(); // 模拟读操作
Thread.sleep(1000);
return value;
} finally {
lock.unlock();
}
}
public void handleWrite(Lock lock, int index) throws InterruptedException {
try {
lock.lock(); // 模拟写操作
Thread.sleep(1000);
value = index;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
final ReadWriteLockDemo demo = new ReadWriteLockDemo();
Runnable readRunnable = new Runnable() {
@Override
public void run() {
try {
demo.handleRead(readLock);
// demo.handleRead(lock);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Runnable writeRunnable = new Runnable() {
@Override
public void run() {
try {
demo.handleWrite(writeLock, new Random().nextInt());
// demo.handleWrite(lock, new Random().nextInt());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
for (int i = 0; i < 18; i++) {
new Thread(readRunnable).start();
}
for (int i = 18; i < 20; i++) {
new Thread(writeRunnable).start();
}
}
}
倒计数器:CountDownLatch
CountDownLatch是一个非常有用的多线程控制工具类。CountDown意为倒数,Latch意为门阀。这个工具通常用来控制线程等待,可以让某一个线程等待直到倒计数结束,再开始执行。
在Java多线程设计模式——Latch设计模式中对Latch设计模式进行了讨论。下面给出一个例子,模拟火箭发射时的检查任务。
public class CountDownLatchDemo implements Runnable {
static final CountDownLatch end = new CountDownLatch(5);
static final CountDownLatchDemo demo = new CountDownLatchDemo();
@Override
public void run() {
try{
//模拟检查任务
Thread.sleep(new Random().nextInt(10)*1000);
System.out.println("check complete");
end.countDown();
}catch (InterruptedException e){
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newFixedThreadPool(5);
for (int i=0;i<5;i++){
exec.submit(demo);
}
// 等待检查
end.await();
// 发射火箭
System.out.println("Fire!");
exec.shutdown();
}
}
output:
check complete
check complete
check complete
check complete
check complete
Fire!
循环栅栏:CyclicBarrier
线程阻塞工具类:LockSupport
Guava和RateLimiter限流
线程的复用:线程池
在实际生产中,线程的数量必须得到控制。盲目创建大量线程对系统是有害的。
Executor框架
下图为Executor框架的结构图。ThreadPoolExecutor和ScheduledThreadPoolExecutor表示一个线程池,Executors扮演线程池工厂的角色,通过Executors可以获得一个特定功能的线程池。
线程池的创建
ThreadPoolExecutor
通过ThreadPoolExecutor创建一个自定义的线程池:
一个包含全部参数的构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize:指定了线程池中线程的数量。
- maximumPoolSize:指定了线程池中的最大线程数量。
- keepAliveTime:当线程池中线程数量超过corePoolSize时,多余的空闲线程的存活时间,即超过corePoolSize的空闲线程在多产时间会被销毁。
- unit:keepAliveTime的单位。
- workQueue:任务队列,被提交但尚未被执行的任务。
- threadFactory:线程工厂,用于创建线程,可对任务进行扩展,一般用默认即可。
- handler:拒绝策略。当任务太多来不及处理时,如何拒绝任务。
Executors
通过Executors可以获得一个特定功能的线程池(尽量使用ThreadPoolExecutor创建线程池)
ExecutorService threadPool = null;
threadPool = Executors.newCachedThreadPool();//有缓冲的线程池,线程数 JVM 控制
threadPool = Executors.newFixedThreadPool(3);//固定大小的线程池
threadPool = Executors.newScheduledThreadPool(2);
threadPool = Executors.newSingleThreadExecutor();//单线程的线程池,只有一个线程在工作
newFixedThreadPool() 方法:返回一个固定线程数量的线程池。若无空闲线程,新的任务会被暂存在一个任务队列中。
newSingleThreadPool() 方法:返回一个只有一个线程的线程池。若无空闲线程,新的任务会被暂存在一个任务队列中。
newCachedThreadPool() 方法:返回一个可根据实际情况调节线程数量的线程池。若有空闲线程,则会优先使用可复用的线程。若没有则会创建新的线程处理任务。
newScheduledThreadPool() 方法:返回一个ScheduledExecutorService对象。ScheduledExecutorService接口在ExecutorService接口的基础上扩展了在给定时间执行某任务的功能,如在某个固定的延时之后执行,或者周期性执行某个任务。
newSingleThreadScheduledExecutor() 方法:返回一个ScheduledExecutorService对象,线程池的大小为1。
计划任务
ScheduledExecutorService对象可根据时间需要对线程进行调度,主要有以下方法:
schedule() 方法:会在给定时间对任务进行一次调度。
scheduleAtFixedRate() 方法:创建一个周期性任务。任务开始于给定的初始时延,后续的任务按照给定的周期执行,即 initialDelay+n*period。
scheduleWithFixedDelay() 方法:创建一个周期性任务。任务开始于一个初始时延,后续的任务会按照给定的时延进行。
public class ScheduledExecutorServiceDemo {
public static void main(String[] args) {
ScheduledExecutorService ses = Executors.newScheduledThreadPool(10);
ses.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try{
Thread.sleep(1000);
System.out.println(System.currentTimeMillis()/1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}, 0, 2, TimeUnit.SECONDS);
}
}
拒绝策略:Handler
JDK内置四种拒绝策略,均为ThreadPoolExecutor的内部类。
AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作。
CallerRunsPolicy策略:只要线程池未关闭,该策略直接在调用者的线程中,运行当前丢弃的任务。该方式不会真的丢弃任务,但是,任务提交线程的性能可能会急剧下降。
DiscardOldestPolicy策略:该策略将丢弃最老的一个请求,也就是即将被执行的任务,并尝试再次提交当前任务。
DiscardPolicy策略:该策略默默丢弃无法处理的任务,不予任何处理。
//拒绝策略
RejectedExecutionHandler rejected = null;
rejected = new ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务抛出异常
rejected = new ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务不异常
rejected = new ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列
rejected = new ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务
以上内置的策略均实现了RejectedExecutionHandler接口,若上述策略无法满足实际应用的需求,可以自行扩展RejectedExecutionHandler接口。接口的定义如下:
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
其中r为请求执行的任务,executor为当前线程池。
public class RejectThreadPoolDemo {
public static class MyTask implements Runnable {
@Override
public void run() {
System.out.println(System.currentTimeMillis() + ":Thread ID:" + Thread.currentThread().getId());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
MyTask task = new MyTask();
ExecutorService es = new ThreadPoolExecutor(
5, 5,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(10),
Executors.defaultThreadFactory(),
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println(r.toString()+" is discard");
}
});
for (int i =0 ;i< Integer.MAX_VALUE;i++){
es.submit(task);
Thread.sleep(10);
}
}
}
阻塞队列:BlockingQueue
参数workQueue是一个BlockingQueue接口的对象,用于存放Runnable对象。BlockingQueue有主要有以下实现类:
- 直接提交的队列(SynchronousQueue):
- 有界任务队列(ArrayBlockingQueue):该队列在创建时必须传入一个容量参数(capacity),表示该队列的最大容量。线程池中的实际线程数量小于corePoolSize时,优先创建新的线程。大于corePoolSize时,将新任务加入等待队列。若无法加入,则创建新的线程。当线程数量大于maximumPoolSize时,执行拒绝策略。
- 无界的任务队列(LinkedBlockingQueue):与有界任务队列相比,无接任务队列不存在任务入队失败的情况,除非系统资源耗尽。当新任务到来时,若线程数小于corePoolSize,创建新的线程。大于corePoolSize之后,线程数不会再增加。若仍有新的任务进入且没有空闲线程,则加入等待队列。
- 优先任务队列(PriorityBlockingQueue):优先任务队列是带有优先级的队列,可以控制任务执行的先后顺序。ArrayBlockingQueue和LinkedBlockingQueue都按照先进先出的方式处理任务,而PriorityBlockingQueue可根据任务自身的优先级顺序先后执行。
回顾:
newFixedTreadPool() 方法,返回的corePoolSize和maximumPoolSize相等、使用了LinkedBlockingQueue任务队列的线程池。
newSingleThreadPool() 方法,是newFixedTreadPool() 的退化,线程数为1。
newCachedThreadPool() 方法,返回corePoolSize为0、maximumPoolSize为无穷大、使用SynchronousQueue的线程池。
自定义创建线程:ThreadFactory
ThreadFactory是一个接口,包含一个用来创建线程的方法,线程池中的线程都是通过ThreadFactory创建的。
public interface ThreadFactory {
Thread newThread(Runnable r);
}
Executors中提供了默认的线程池,可参照改模板自定义ThreadFactory。
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
自定义线程池的ThreadFactory可以帮助我们做以下扩展功能:
- 跟踪线程池在何时创建了多少线程。
- 自定义线程池的名字、组合优先级。
- 设置线程池为守护线程(可以看到DefaultThreadFactory 将线程设置为非守护)。
- …
扩展任务
虽然JDK并发包已经为我们提供了高性能的线程池,但仍有一些需求需要对线程池进行扩展,例如监控每个任务执行的开始时间和结束时间或者其他一些自定义的功能。
在Java多线程设计模式——监控任务的生命周期中对如何实现对线程监控扩展进行了讨论。
下图是ThreadPoolExecutor.runWorker() 方法中实现扩展的主要逻辑代码。
try {
beforeExecute(wt, task);
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);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
ThreadPoolExecutor为扩展任务,提供了下面三个方法。
protected void beforeExecute(Thread t, Runnable r) { }
protected void afterExecute(Runnable r, Throwable t) { }
protected void terminated() { }
在创建ThreadPoolExecutor时可对三个方法进行重写。
public class ExtThreadPool {
public static class MyTask implements Runnable {
public String name;
public MyTask(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println("正在执行" + ":Thread ID:" + Thread.currentThread().getId() + ",Task Name=" + name);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService es =new ThreadPoolExecutor(5, 5,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()){
@Override
protected void beforeExecute(Thread t,Runnable r){
System.out.println("准备执行:"+((MyTask)r).name);
}
@Override
protected void afterExecute(Runnable r,Throwable t){
System.out.println("执行完成:"+((MyTask)r).name);
}
@Override
protected void terminated(){
System.out.println("线程退出");
}
};
for (int i =0;i<5;i++){
MyTask task = new MyTask("TASK-GETM-"+i);
es.execute(task);
Thread.sleep(10);
}
es.shutdown();
}
}
分而治之:Fork/Join框架
Fork/Join类似于MapReduce,用来有效处理大量的数据。Fork意为分叉,在Linux中fork() 方法用来创建子进程,java沿用类似的命名方法。join表示等待,等待该分支运行结束。Fork/Join框架的执行逻辑如下图所示:
提交的任务和线程数量并不是一一对应的。绝大多数情况下,一个物理线程需要处理多个任务逻辑,因此每个线程都有一个任务队列。此外还有一种情况,若线程A空闲,线程B繁忙,则线程A会从线程B的队列当中取走一个任务,达到动态平衡。值得注意的是,当一个线程视图帮助其他线程时,总是从队列的底部获取任务,避免了数据的冲突。
ForkJoinTaskyou两个重要子类。
1到200 000求和的例子。
public class CountTask extends RecursiveTask<Long> {
private static final int THRESHOLD = 10000;
private long start;
private long end;
public CountTask(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long sum = 0;
boolean canCompute = (end - start) < THRESHOLD;
if (canCompute) {
for (long i = start; i <= end; i++) {
sum+=i;
}
}else {
//分成100个小任务
long step = (start + end)/100;
ArrayList<CountTask> subTasks = new ArrayList<>();
long pos = start;
for (int i =0;i<100;i++){
long lastOne = pos + step;
if (lastOne > end){
lastOne = end;
}
CountTask subTask = new CountTask(pos, lastOne);
pos+=step+1;
subTasks.add(subTask);
subTask.fork();
}
for (CountTask t:subTasks){
sum += t.join();
}
}
return sum;
}
public static void main(String[] args) {
ForkJoinPool forkJoinPool = new ForkJoinPool();
CountTask task = new CountTask(0,200000L );
ForkJoinTask<Long> result = forkJoinPool.submit(task);
try{
long res = result.get();
System.out.println("sum="+res);
}catch (InterruptedException e){
e.printStackTrace();
}catch (ExecutionException e){
e.printStackTrace();
}
}
}