线程应用:(八)Semaphere信号灯、CyclicBarrier汇合、CountDownLatch计数器、Exchanger
一、Semaphere信号灯
Semaphere与互斥锁的区别:有多个线程时,只有获得互斥锁的那个才能进入,而信号灯是可以同时多个线程一起运行的,信号灯为1时相当于互斥锁。
等待的线程准备获得信号灯的顺序是随机的,但可以设置先到先得。
假设有10个线程,但只有3个信号灯,能实现并发访问的线程数只能为3,剩下的7个线程要等待获得信号灯才能运行,代码如下。
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; public class SemaphoreTest { public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); //创建缓存线程池 final Semaphore sp = new Semaphore(3); //创建信号灯个数为3,为1相当于互斥 for(int i=0;i<10;i++){ Runnable runnable = new Runnable() { //创建10个线程,但只有3个信号灯,实际只有3个线程运行 @Override public void run() { try { sp.acquire(); //获得信号灯 System.out.println(Thread.currentThread().getName()+"进入"+", 并发数:"+(3-sp.availablePermits())); Thread.sleep((long)(Math.random()*10000)); System.out.println(Thread.currentThread().getName()+"准备离开"+", 并发数:"+(3-sp.availablePermits())); sp.release(); //释放信号灯,其他线程可进入 System.out.println(Thread.currentThread().getName()+"已离开"+", 并发数:"+(3-sp.availablePermits())); } catch (InterruptedException e) { e.printStackTrace(); } } }; service.execute(runnable); //把任务提交到线程池 } } }
二、CyclicBarrier汇合
大家彼此等待,先到的会先等待,大家集合好后才开始出发。可以反复使用,await后重新计数。
import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class CyclicBarrierTest { public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); final CyclicBarrier cb = new CyclicBarrier(3); //设定3个线程到齐后才继续往下走 for(int i=0;i<3;i++){ Runnable runnable = new Runnable() { @Override public void run() { try { Thread.sleep((long)(Math.random()*10000)); System.out.println(Thread.currentThread().getName()+"即将到达集合点1"+", 当前已到:"+(cb.getNumberWaiting()+1)); cb.await(); //表示在这里集合,如果没有到齐则等待,到齐了才往下走 Thread.sleep((long)(Math.random()*10000)); System.out.println(Thread.currentThread().getName()+"即将到达集合点2"+", 当前已到:"+(cb.getNumberWaiting()+1)); cb.await(); Thread.sleep((long)(Math.random()*10000)); System.out.println(Thread.currentThread().getName()+"即将到达集合点3"+", 当前已到:"+(cb.getNumberWaiting()+1)); cb.await(); } catch (Exception e) { e.printStackTrace(); } } }; service.execute(runnable); //把任务提交到线程池 } } }
三、CountDownLatch计数器
类似倒计时计数器,当计数到达0时,才让所有等待者或单个等待者开始执行。
import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class CountDownLatchTest { public static void main(String[] args) throws InterruptedException { ExecutorService service = Executors.newCachedThreadPool(); final CountDownLatch oneDown = new CountDownLatch(1); final CountDownLatch threeDown = new CountDownLatch(3); for(int i=0;i<3;i++){ Runnable runnable = new Runnable() { @Override public void run() { try { oneDown.await(); //第二步:3个线程会在这里等,直到oneDown的值减为0,才会接着进行 Thread.sleep((long)(Math.random()*10000)); threeDown.countDown(); //第三步:每当有线程走到这,threeDown减1 } catch (Exception e) { e.printStackTrace(); } } }; service.execute(runnable); } Thread.sleep((long)(Math.random()*10000)); oneDown.countDown(); //第一步:oneDown先减为0,让上面的3个线程走 threeDown.await(); //第四步:直到上面3个线程把threeDown减为0才继续通行 service.shutdown(); } }
四、Exchanger
可以实现两个线程间的数据交换,两者都到达exchanger.exchange()方法后,才交换数据,然后各自继续进行。
import java.util.concurrent.Exchanger; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ExchangerTest { public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); final Exchanger<String> exchanger = new Exchanger<String>(); service.execute(new Runnable() { @Override public void run() { try { String data1 = "AAAAA"; System.out.println(Thread.currentThread().getName()+"准备换的数据:"+data1); Thread.sleep((long)(Math.random()*10000)); String data2 = exchanger.exchange(data1); //线程1准备把data1换成data2 System.out.println(Thread.currentThread().getName()+"换回的数据:"+data2); } catch (InterruptedException e) { e.printStackTrace(); } } }); service.execute(new Runnable() { @Override public void run() { try { String data1 = "BBBBB"; System.out.println(Thread.currentThread().getName()+"准备换的数据:"+data1); Thread.sleep((long)(Math.random()*10000)); String data2 = exchanger.exchange(data1); //线程1准备把data1换成data2 System.out.println(Thread.currentThread().getName()+"换回的数据:"+data2); } catch (InterruptedException e) { e.printStackTrace(); } } }); } }