随笔,笔记
Fork-Join 框架
fork-join 可以将一个任务分解为 多个子任务。适用于 密集型计算。
提供了两个抽象类:
RecursiveTask<T>
:计算会生成一个类型为 T 的结果。RecursiveAction
:计算不会生成结果。
实现 这个两个抽象类的其中一个,并 重载 compute 方法来生成并调用 子任务。
public class Counter extends RecursiveTask<Integer> {
private int counter;
public Counter(int counter) {
this.counter = counter;
}
@Override
protected Integer compute() {
// 进行 递归 分解
if (this.counter == 100) {
Counter first = new Counter(counter - 1);
Counter second = new Counter(counter + 1);
// 接收 多个任务,并阻塞,知道所有任务 都已经完成
invokeAll(first, second);
// join 方法返回 子任务 计算的结果
return first.join() + second.join();
}
return this.counter;
}
}
测试
public class MainTest {
public static void main(String[] args) {
// 创建 任务
Counter counter = new Counter(100);
//
ForkJoinPool pool = new ForkJoinPool();
// 执行给定的任务,完成后返回其结果。
Integer number = pool.invoke(counter);
System.out.println(number);
}
}
感觉 fork-join 考验 递归思想,归并思想。
信号量
- 概念上讲,一个信号量管理许多的许可证。为了通过信号量,线程通过调用 acquire 请求许可。
- 信号量是维护一个计数。由于许可证的数量是固定的,所以可以限制通过的线程数量。
- 其他线程可以 通过 release 释放许可证。不是必须由调用它的线程释放,任何线程都可以释放任意数目的许可证。
- Dijkstra 指出 信号量可以被有效地实现,并且有足够的能力解决许多常见的线程同步问题。
倒计时门栓
- 一个倒计时门栓,让一个线程集等待 ,直到计时变为 0。
- 倒计时门栓 是一次性,一旦变为 0,就不能再重用了。
public class MainTest {
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(3);
new Thread(()->{
try {
// 当 计数器 不为 0 时,等待
// 当计数器 变为 0 继续运行
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
},"线程1").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName());
// 计数器 减一
latch.countDown();
},"线程2").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName());
// 计数器 减一
latch.countDown();
},"线程3").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName());
// 计数器 减一
latch.countDown();
},"线程4").start();
}
}
障栅(屏障)
- 障栅,CyclicBarrier类实现的一个集结点。
- 画一条线,线程运行到 线的位置,就等一等,等所有线程都到达这条线,就解除屏障,继续运行。
- 适合 那些把 所有部分都准备好,把结果组合起来 的应用。
public class MainTest {
public static void main(String[] args) {
// 构建一个 屏障,指定需要参与的线程数,本次是指定的 3
CyclicBarrier barrier = new CyclicBarrier(3);
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"执行完成,进入等待");
try {
// 设定一个 屏障
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"线程结束");
},"线程1").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"执行完成,进入等待");
try {
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"线程结束");
},"线程2").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"执行完成,进入等待");
try {
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"线程结束");
},"线程3").start();
}
}
- 线程 凑够 指定的线程数,就接触屏障。
- 可以提供一个屏障动作,当所有线程到达屏障的时候 执行这个动作
// 构建一个 屏障,指定需要参与的线程数,本次是指定的 3
CyclicBarrier barrier = new CyclicBarrier(2,()->{
System.out.println("凑够了");
});
- 障栅 是循环的,当等待线程被释放后会被 重用。
交换器
- 当两个线程在同一个数据缓冲区的两个实例上工作的时候,可以使用交换器。
- 一个线程向缓冲区填入数据,另一个线程消耗这些数据。当他们都完成以后,相互交换缓冲区。
代码理解:
public class MainTest {
public static void main(String[] args) {
// 构建 交换器
Exchanger<Integer> exchanger = new Exchanger<>();
new Thread(()->{
Integer integer = 100;
try {
System.out.println(Thread.currentThread().getName()+"线程开始交换:"+integer);
// 进行交换 ,并返回 交换之后的值
integer = exchanger.exchange(integer);
System.out.println(Thread.currentThread().getName()+"线程交换完成:"+integer);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程1").start();
new Thread(()->{
Integer integer = 200;
try {
System.out.println(Thread.currentThread().getName()+"线程开始交换:"+integer);
// 进行交换 ,并返回 交换之后的值
integer = exchanger.exchange(integer);
System.out.println(Thread.currentThread().getName()+"线程交换完成:"+integer);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程2").start();
}
}
- 线程数量 必须要 偶数,为奇数时,有一个线程 交换,一直等待。
- 可重复使用
同步队列
- 同步队列是一种将生产者与消费者线程配对的机制。
- 当一个线程 调用
SynchronousQueue
的put 方法时,它会阻塞直到另一个线程调用 take 方法为止。
public class MainTest {
public static void main(String[] args) {
SynchronousQueue<Integer> queue = new SynchronousQueue<>();
new Thread(()->{
Integer integer = 100;
try {
System.out.println(Thread.currentThread().getName()+"线程添加一个值:"+integer);
queue.put(integer);
System.out.println(Thread.currentThread().getName()+"线程 解除 阻塞");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程1").start();
new Thread(()->{
try {
Thread.sleep(3000);
Integer integer = queue.take();
System.out.println(Thread.currentThread().getName()+"线程 获取此值:"+integer);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程2").start();
}
}
SynchronousQueue
的 size 方法总是 返回 0;
类 | 作用 | 场景 |
---|---|---|
CyclicBarrier | 允许线程集等待,直到其中预定数目的线程到达一个 公共障栅,然后结束线程的等待,继续执行 | 当大量线程需要在它们结果可用之前完成时 |
Phaser | 和CyclicBarrier 差不多,但是它允许动态改变预定的线程数目 | Java7 引入 |
CountDownLatch | 允许线程集等待直到计数器减为 0 | 当一个或多个线程需要等待直到指定数目的事件发生 |
Exchanger | 允许两个线程交换对象,在 对象准备好时 | 当两个线程工作在同一数据结构的两个实例上的时候,一个向实例添加数据而另一个从实例清除数据 |
Semaphore | 允许线程集等待直到被允许继续执行为止 | 限制访问资源的线程总数。 |
SynchronousQueue | 允许一个线程 把对象 交给另一个线程 | 在没有显示同步的情况下,当两个线程准备好将一个对象从一个线程传递到另一个时 |