ReentrantLock
ReentrantReadWriteLock
CountDownLatch (信号量)
CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信;
主要方法:
构造一个用给定计数初始化的 CountDownLatch。
CountDownLatch(int count)
// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。
void await()
// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。
boolean await(long timeout, TimeUnit unit)
// 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
void countDown()
// 返回当前计数。
long getCount()
// 返回标识此锁存器及其状态的字符串。
String toString()
使用方式:
private static CountDownLatch doneSignal;
public static void main(String[] args) {
try {
doneSignal = new CountDownLatch(LATCH_SIZE);
// 新建5个任务
for(int i=0; i<LATCH_SIZE; i++)
new InnerThread().start();
System.out.println("main await begin.");
// "主线程"等待线程池中5个任务的完成
doneSignal.await();
System.out.println("main await finished.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class InnerThread extends Thread{
public void run() {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " sleep 1000ms.");
// 将CountDownLatch的数值减1
doneSignal.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
CyclicBarrier(栅栏)
所有线程会等待全部线程到达栅栏之后才会继续执行,并且最后到达的线程会完成 Runnable 的任务。
可以用于多线程计算数据,最后合并计算结果的场景。
主要方法:
//初始化 parties 是参与线程的个数
//第二个构造方法有一个 Runnable 参数,这个参数的意思是最后一个到达线程要做的任务
public CyclicBarrier(int parties)
public CyclicBarrier(int parties, Runnable barrierAction)
//表示自己已经到达栅栏
public int await() throws InterruptedException, BrokenBarrierException
//表示自己已经到达栅栏 并且最多等待timeout时间
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException
使用方法:
static class TaskThread extends Thread {
CyclicBarrier barrier;
public TaskThread(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println(getName() + " 到达栅栏 A");
barrier.await();
System.out.println(getName() + " 冲破栅栏 A");
Thread.sleep(2000);
System.out.println(getName() + " 到达栅栏 B");
barrier.await();
System.out.println(getName() + " 冲破栅栏 B");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
int threadNum = 5;
CyclicBarrier barrier = new CyclicBarrier(threadNum, new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 完成最后任务");
}
});
for(int i = 0; i < threadNum; i++) {
new TaskThread(barrier).start();
}
}
CountDownLatch和CyclicBarrier的区别:
(1) CountDownLatch的作用是允许1或N个线程等待其他线程完成执行;而CyclicBarrier则是允许N个线程相互等待。
(2) CountDownLatch参与的线程的职责是不一样的,有的在倒计时,有的在等待倒计时结束。CyclicBarrier 参与的线程职责是一样的。
(3) CountDownLatch的计数器无法被重置;CyclicBarrier的计数器可以被重置后使用,因此它被称为是循环的barrier。
Semaphore 信号量
比喻:相当于一个阀门,来控制某时刻的流量;比如高速路收费站;
举例:用来控制同时执行的最大线程数;常用于限流;
用法:初始化时指定最大信号量,线程调用acquire方法,表接下来一段时间这一通道会有自己参与占用;如果acquire时已经满了,就阻塞住;调用release方法表示释放占用;
注意:支持公平和非公平模式;
Exchanger 交换器
用于两个线程间的唤醒,并值传递;
过程:
1. Exchanger内部有两个位置,当第一个线程A执行了exchange方法后,将传入的值放入第一个位置,计为A1,然后A进入阻塞状态,直到有其他线程也来执行了exchange方法为止;
2. 然后,线程B也来执行exchange方法,首先将传入的值写到第二个位置A2,然后交换A1和A2的值,接下来两个线程分别向下执行;
LockSupport
主要对标wait()/notify();
主要用法:park()、unpark(),内部不是锁,调用了之后立马停止或者恢复;
注意,unpark()可以先于park()执行,效果是:程序不会暂停,即park()不生效;
区别:LockSupport可以启动指定线程,而wait/notify要做到这一点确是非常麻烦;
Fork/Join框架 -- ForkJoinPool
ForkJoinPool 是 JDK1.7 开始提供的线程池。为了解决 CPU 负载不均衡的问题。如某个较大的任务,被一个线程去执行,而其他线程处于空闲状态;
如上所示,forkjoin框架是为了加大任务做拆分然后交由多个子线程去执行,然后返回结果。
第一步分割任务。首先我们需要有一个fork类来把大任务分割成子任务,有可能子任务还是很大,所以还需要不停的分割,直到分割出的子任务足够小。
第二步执行任务并合并结果。分割的子任务分别放在双端队列里,然后几个启动线程分别从双端队列里获取任务执行。子任务执行完的结果都统一放在一个队列里,启动一个线程从队列里拿数据,然后合并这些数据。
ForkJoinPool将一个任务拆分成多个“小任务”并行计算,再把多个“小任务”的结果合成总的计算结果。ForkJoinPool是ExecutorService的一个实现类,所以它是一个线程池;
使用方法:
//创建一个包含parallelism个并行线程的ForkJoinPool
public ForkJoinPool(int parallelism);
//以Runtime.getRuntime().availableProcessors()的返回值作为parallelism来创建ForkJoinPool
public ForkJoinPool();
ForkJoinPool提供了一系列invoke、submit和excute方法,来执行指定任务,一般地,调用ForkJoinPool的submit(ForkJoinTask task)或者invoke(ForkJoinTask task)来执行指定任务。其中ForkJoinTask代表一个可以并行、合并的任务。ForkJoinTask是一个抽象类,它有两个抽象子类:RecursiveAction和RecursiveTask。
- RecursiveTask代表有返回值的任务
- RecursiveAction代表没有返回值的任务。
RecursiveAction的示例:
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.TimeUnit;
//RecursiveAction为ForkJoinTask的抽象子类,没有返回值的任务
class PrintTask extends RecursiveAction {
// 每个"小任务"最多只打印50个数
private static final int MAX = 50;
private int start;
private int end;
PrintTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected void compute() {
// 当end-start的值小于MAX时候,开始打印
if ((end - start) < MAX) {
for (int i = start; i < end; i++) {
System.out.println(Thread.currentThread().getName() + "的i值:"
+ i);
}
} else {
// 将大任务分解成两个小任务
int middle = (start + end) / 2;
PrintTask left = new PrintTask(start, middle);
PrintTask right = new PrintTask(middle, end);
// 并行执行两个小任务
left.fork();
right.fork();
}
}
}
public class ForkJoinPoolTest {
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
// 创建包含Runtime.getRuntime().availableProcessors()返回值作为个数的并行线程的ForkJoinPool
ForkJoinPool forkJoinPool = new ForkJoinPool();
// 提交可分解的PrintTask任务
forkJoinPool.submit(new PrintTask(0, 200));
forkJoinPool.awaitTermination(2, TimeUnit.SECONDS);//阻塞当前线程直到 ForkJoinPool 中所有的任务都执行结束
// 关闭线程池
forkJoinPool.shutdown();
}
}
RecursiveTask示例:
class SumTask extends RecursiveTask<integer> {
// 每个"小任务"最多只打印50个数
private static final int MAX = 20;
private int arr[];
private int start;
private int end;
SumTask(int arr[], int start, int end) {
this.arr = arr;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
int sum = 0;
// 当end-start的值小于MAX时候,开始打印
if ((end - start) < MAX) {
for (int i = start; i < end; i++) {
sum += arr[i];
}
return sum;
} else {
System.err.println("=====任务分解======");
// 将大任务分解成两个小任务
int middle = (start + end) / 2;
SumTask left = new SumTask(arr, start, middle);
SumTask right = new SumTask(arr, middle, end);
// 并行执行两个小任务
left.fork();
right.fork();
// 把两个小任务累加的结果合并起来
return left.join() + right.join();
}
}
}
public class ForkJoinPoolTest2 {
public static void main(String[] args) throws Exception {
int arr[] = new int[100];
Random random = new Random();
int total = 0;
// 初始化100个数字元素
for (int i = 0; i < arr.length; i++) {
int temp = random.nextInt(100);
// 对数组元素赋值,并将数组元素的值添加到total总和中
total += (arr[i] = temp);
}
System.out.println("初始化时的总和=" + total);
// 创建包含Runtime.getRuntime().availableProcessors()返回值作为个数的并行线程的ForkJoinPool
ForkJoinPool forkJoinPool = new ForkJoinPool();
// 提交可分解的PrintTask任务
Future<integer> future = forkJoinPool.submit(new SumTask(arr, 0,
arr.length));
System.out.println("计算出来的总和=" + future.get());
// 关闭线程池
forkJoinPool.shutdown();
}
}