CountDownLatch
核心函数
// 构造函数
public CountDownLatch(int count) ;// count为内部计数
public void await()
调用await后,该线程会被阻塞,直到count计数为0;或其他线程调用的该线程的interrupt方法,中断了该线程。
public boolean await(long timeout, TimeUnit unit);
和无差await()
方法相识,但不会一直处于阻塞状态,当设置的时间到了之后,直接返回(false)。
public void countDown()
计数(count)减一,如果减一后count的值为0则,唤醒因为await
方法阻塞的线程。
举例
public class CountDownLatchTest {
private static ExecutorService executorService = Executors.newCachedThreadPool();
public static void main(String[] args) throws Exception{
}
/**
* CountDownLatch:一个线程等待其他线程运行都完后才执行
* 生活中的例子:图书馆闭馆时,管理员需要等待所有的同学都离开后才关闭图书馆。
*/
private static void countDownLatchTestDemo1() throws Exception {
/* 假设图书馆今天有10个同学 */
CountDownLatch downLatch = new CountDownLatch(10);
for (int i = 1; i <= 10; i++) {
String studentName = i + "同学";
executorService.submit(() -> {
System.out.println(studentName + "离开了图书馆");
downLatch.countDown();
});
}
/* (当前线程)管理员等待同学都离开图书馆*/
downLatch.await();
System.out.println("可以关门了");
}
}
补充
CountDownLatch 与 join
- CountDownLatch 先对于 join 而言,在使用上CountDownLatch 更加灵活,方便。
- join 方法不好与线程池结合(线程池中我们直接是
submit(Runnable接口)
)不好获取执行的线程,就不好使用join方法。 - 对于多个线程同步问题,join的书写方式不太美观。例如我们需要等待3个子线程执行完后才结束main线程。
public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(() -> { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "线程:执行完毕"); }); Thread thread2 = new Thread(() -> { System.out.println(Thread.currentThread().getName() + "线程:执行完毕"); }); Thread thread3 = new Thread(() -> { System.out.println(Thread.currentThread().getName() + "线程:执行完毕"); }); thread3.start(); thread1.start(); thread2.start(); /*等待线程thread1执行完毕*/ thread1.join(); /*等待线程thread2执行完毕*/ thread2.join(); /*等待线程thread3执行完毕*/ thread3.join(); // 当需要做同步处理的线程较多时,代码就会显得很‘丑’ System.out.println("主线程执行完毕"); }
CyclicBarrier
cyclicBarrier:回环屏障
由于CountDownLatch是”一次性“的,当计数器变为0后,由于CountDownLatch的await
,countdown
就会立即返回,失去的同步的作用。而CyclicBarrier可以理解位CountDownLatch的增强,可以多次使用。当一组线程达到一个状态全部执行后,会重置CyclicBarrier的状态(count=0)。
核心函数
public int await()
调用await后,该线程会被阻塞,直到count计数为0;或其他线程调用的该线程的interrupt方法,中断了该线程。
public boolean await(long timeout, TimeUnit unit);
和无差await()
方法相识,但不会一直处于阻塞状态,当设置的时间到了之后,直接返回(false)。
举例
public class CyclicBarrierTest {
private static ExecutorService executorService = Executors.newCachedThreadPool();
public static void main(String[] args) {
cyclicBarrierDemo1();
}
/**
* <p>
* 生活中的例子:公交车。
* 记得原来有这样的公交车站:坐满就发车(没满就等),就有点符合CyclicBarrier。
*/
public static void cyclicBarrierDemo1() {
/* 假设公交车只有10座位 */
final Integer num = 10;
CyclicBarrier cyclicBarrier = new CyclicBarrier(num, () -> {
System.out.println("发车啦,gogogo");
});
// 一共有230人 ==> 23次
for (int i = 0; i < 230; i++) {
executorService.submit(() -> {
try {
/* 上车等待发车(座位-1) */
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
}
Semaphore
Semaphore( 信号量)与前面连个不太一样。Semaphore的内部的计数器是递增的(前两个都是递减的),
所以不需要知道需要同步的线程数。可以回忆一下,上面的两个举例中我们都有添加假设情况(假设图书馆今天有10个同学,假设公交车只有10座位),这也间接指明的需要同步的线程数。
核心方法
- acquire
public void acquire()
通过 acquire() 获取一个许可(信号量资源),如果当前的许可的个数大于0,则为该线程颁发许可,且可用许可数-1。如果当前的许可的个数等于0,则阻塞(等待其他线程释放许可)。
public void acquire(int permits)
获取permits个个许可
- release
public void release()
释放一个许可,且可用许可数+1
public void release(int permits)
释放permits个许可,且可用许可数+permits
应用
按序打印: https://leetcode-cn.com/problems/print-in-order/
题目描述
三个不同的线程 A、B、C 将会共用一个 Foo 实例。
线程 A 将会调用 first() 方法
线程 B 将会调用 second() 方法
线程 C 将会调用 third() 方法
请设计修改程序,以确保 second() 方法在 first() 方法之后被执行,third() 方法在 second() 方法之后被执行。
方法一
volatile 方式
class Foo {
static volatile Integer flag = 1;
public Foo() {}
public void first(Runnable printFirst) throws InterruptedException {
while(flag!=1){}
// printFirst.run() outputs "first". Do not change or remove this line.
printFirst.run();
flag=2;
}
public void second(Runnable printSecond) throws InterruptedException {
while(flag!=2){ }
// printSecond.run() outputs "second". Do not change or remove this line.
printSecond.run();
flag=3;
}
public void third(Runnable printThird) throws InterruptedException {
while(flag!=3){}
// printThird.run() outputs "third". Do not change or remove this line.
printThird.run();
flag=1;
}
}
方式二
Semaphore方式
import java.util.concurrent.*;
class Foo {
Semaphore firstSemaphore = new Semaphore(1);
Semaphore secondSemaphore = new Semaphore(0);
Semaphore thirdSemaphore = new Semaphore(0);
public Foo() {}
public void first(Runnable printFirst) throws InterruptedException {
firstSemaphore.acquire(1);
// printFirst.run() outputs "first". Do not change or remove this line.
printFirst.run();
secondSemaphore.release(1);
}
public void second(Runnable printSecond) throws InterruptedException {
secondSemaphore.acquire(1);
// printSecond.run() outputs "second". Do not change or remove this line.
printSecond.run();
thirdSemaphore.release(1);
}
public void third(Runnable printThird) throws InterruptedException {
thirdSemaphore.acquire(1);
// printThird.run() outputs "third". Do not change or remove this line.
printThird.run();
firstSemaphore.release(1);
}
}