1、Semaphore
-
Semaphore是一个计数信号量,它的本质是一个"共享锁"。
-
信号量维护了一个信号量许可集。线程可以通过调用acquire()来获取信号量的许可;当信号量中有可用的许可时,线程能获取该许可;否则线程必须等待,直到有可用的许可为止。 线程可以通过release()来释放它所持有的信号量许可。
-
Semaphore是一种在多线程环境下使用的设施,该设施负责协调各个线程,以保证它们能够正确、合理的使用公共资源的设施,也是操作系统中用于控制进程同步互斥的量。Semaphore是一种计数信号量,用于管理一组资源,内部是基于AQS的共享模式。它相当于给线程规定一个量从而控制允许活动的线程数。
public class SemaphoreDemo {
/**
*
* 假设现在有二十个人去售票厅买票,但是窗口只有两个,那么同时能够买票的只能有两个,当
* 2个人中任意一个人买好票离开之后,等待的18个人中又会有一个人可以占用窗口买票
*/
/**
* 执行任务类,获取信号量和释放信号
*/
class SemaphoreRunnable implements Runnable {
private Semaphore semaphore; //信号量
private int user; //记录第几个用户
public SemaphoreRunnable(Semaphore semaphore, int user) {
this.semaphore = semaphore;
this.user = user;
}
@Override
public void run() {
try {
//获取信号量的许可
semaphore.acquire();
System.out.print("用户 【" + user + "】进入窗口,准备买票...\n");
Thread.sleep((long) (Math.random()*10000));
System.out.print("用户 【" + user + "】买票完成,即将离开...\n");
Thread.sleep((long) (Math.random()*10000));
System.out.print("用户 【" + user + "】离开售票窗口...\n");
//释放资源
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void execute(){
//1: 定义窗口个数
final Semaphore semaphore = new Semaphore(2);
//2: 使用线程池
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
executorService.execute(new SemaphoreRunnable( semaphore,(i+1)));
}
executorService.shutdown();
}
public static void main(String[] args) {
SemaphoreDemo semaphoreDemo = new SemaphoreDemo();
semaphoreDemo.execute();
}
}
执行结果:
用户 【1】进入窗口,准备买票...
用户 【2】进入窗口,准备买票...
用户 【1】买票完成,即将离开...
用户 【2】买票完成,即将离开...
用户 【1】离开售票窗口...
用户 【3】进入窗口,准备买票...
用户 【2】离开售票窗口...
用户 【4】进入窗口,准备买票...
用户 【3】买票完成,即将离开...
用户 【3】离开售票窗口...
用户 【5】进入窗口,准备买票...
用户 【4】买票完成,即将离开...
2、CyclicBarrier
-
CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。
-
CyclicBarrier类似于我们前面看到的CountDownLatch.但是有以下两点不同:
- 1:CountDownLatch是当状态达到后即当计数器为零,所有线程通过后CountDownLatch不可再用。而CyclicBarrier可以当状态达到后,所有线程通过后即可重复使用。
- 2:CountDownLatch等待的是事件。CyclicBarrier等待的是线程.这里解释下等待事件和线程的区别.由于CountDownLatch存在countDown()方法。即对计数器减去1 .
- 假设我们有如下代码:CountDownLatch latch = new CountDownLatch(5);
- 我们在一个线程中对countDown()调用了5次。那么这个CountDownLatch的计数器就减为零。 然后等待的线程就可以通过了。
public class CyclicBarrierDemo {
/**
* 1:线程阻塞,多线程计算数据,合并结果
*/
public static void main(String[] args) {
final CyclicBarrier cyclicBarrier = new CyclicBarrier(3, new Runnable() {
@Override
public void run() {
try {
Thread.sleep((long) (Math.random()*10000));
System.out.print("人员全部到齐,先进行拍照留念....\n");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//2: 使用线程池
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 3; i++) {
final int user = i+1;
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep((long) (Math.random()*10000));
System.out.print("用户 【" + user + "】到达聚餐点,当前已有" + (cyclicBarrier.getNumberWaiting()+1) + "...\n");
// 阻塞
cyclicBarrier.await();
if (user == 1){
System.out.print("人员全部到齐,拍照结束,开始吃饭....\n");
}
Thread.sleep((long) (Math.random()*10000));
System.out.print(user + "吃完饭,准备回家....\n");
} catch (BrokenBarrierException | InterruptedException e) {
e.printStackTrace();
}
}
};
executorService.execute(runnable);
}
executorService.shutdown();
}
}
执行结果:
用户 【2】到达聚餐点,当前已有1...
用户 【3】到达聚餐点,当前已有2...
用户 【1】到达聚餐点,当前已有3...
人员全部到齐,先进行拍照留念....
人员全部到齐,拍照结束,开始吃饭....
3吃完饭,准备回家....
2吃完饭,准备回家....
1吃完饭,准备回家....
3、Exchanger
- 两个线程可以交换对象的同步点。每个线程都在进入exchange方法时给出某个对象,并接受其他线程返回时给出的对象。用于实现两个人之间的数据交换,每个人在完成一定的事务后想与对方交换数据,第一个先拿出数据的人将一直等待第二个人拿着数据到来时,才能彼此交换数据。
public class ExchangeDemo {
/**
* 1:线程之间交换数据
* 2:校对工作
*/
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new Runnable() {
@Override
public void run() {
try {
String returnString = exchanger.exchange("AKyS");
System.out.println("该线程用AKyS进行交换...\n");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
executorService.execute(new Runnable() {
@Override
public void run() {
try {
//必须两个线程同时到达,同时执行。当然可以使用同步锁
Thread.sleep((long) (Math.random()*10000));
String returnString = exchanger.exchange("BLANK");
System.out.println("该线程用BLANK进行交换...\n");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
executorService.shutdown();
}
执行结果:
该线程用BLANK进行交换...
该线程用AKyS进行交换...
4、CountDownLatch
- 当一个线程需要在另外若干个线程执行完毕后再执行时。当然我们也可以使用join()方法来实现,但是这需要去逐个线程等待,而且各个线程是串行运行的。CountDownLatch的实现是等待一个信号量count,每当线程执行一次CountDownLatch.countDown()方法时,CountDownLatch就将count-1,直到count为0时,将唤醒执行CountDownLatch.await()处的线程。
public class CountDownLatchDemo {
/**
* 1: 有一个任务,它需要等待其他某几个任务执行完毕之后才能执行
*/
public static void main(String[] args) {
final CountDownLatch countDownLatch = new CountDownLatch(2);
//任务1
new Thread(){
public void run(){
try {
System.out.println("任务1正在执行...\n");
Thread.sleep((long) (Math.random()*10000));
System.out.println("任务1执行完毕...\n");
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
//任务2
new Thread(){
public void run(){
try {
System.out.println("任务2正在执行...\n");
Thread.sleep((long) (Math.random()*10000));
System.out.println("任务2执行完毕...\n");
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
//主线程
System.out.println("等待其他线程执行完毕,主线程开始执行: " + Thread.currentThread().getName() + "...\n");
try {
countDownLatch.await();
System.out.printf("其他线程执行完毕,主线程执行任务:" + Thread.currentThread().getName()+ "...\n");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行结果:
任务1正在执行...
任务2正在执行...
等待其他线程执行完毕,主线程开始执行: main...
任务2执行完毕...
任务1执行完毕...
其他线程执行完毕,主线程执行任务:main...
转载自:
作者:AKyS佐毅
链接:https://www.jianshu.com/p/bb1dd7946087