首页 > 技术文章 > java 5线程中 Semaphore信号灯,CyclicBarrier类,CountDownLatch计数器以及Exchanger类使用

little-fly 2017-09-27 10:50 原文

先来讲解一下Semaphore信号灯的作用:

 可以维护当前访问自身的线程个数,并提供了同步机制,

使用semaphore可以控制同时访问资源的线程个数

例如,实现一个文件允许的并发访问数。

请看下面的演示代码:

 1 public class SemaphoreTest
 2 {
 3     public static void main(String[] args)
 4     {
 5         //创建一个带有缓存的线程池
 6         ExecutorService service = Executors.newCachedThreadPool();
 7         //创建三个信号灯
 8         final Semaphore sp = new Semaphore(3);//最多并发三个线程  此处可以按照需求去修改
 9         //开启十个线程
10         for (int i = 1; i <= 10; i++)
11         {
12             //只有三个线程可以同时进入    其余线程等待
13             service.execute(new Runnable()
14             {
15                 @Override
16                 public void run()
17                 {
18                     try
19                     {
20                         sp.acquire();//获取一盏信号灯
21                     } catch (InterruptedException e)
22                     {
23                         e.printStackTrace();
24                     }
25                     System.out.println("线程 "+Thread.currentThread().getName()+" 进入"
26                             + " ,当前已有 "+(3-sp.availablePermits())+ " 个并发");
27                     try
28                     {
29                         Thread.sleep(new Random().nextInt(1000));
30                     } catch (InterruptedException e)
31                     {
32                         e.printStackTrace();
33                     }
34                     System.out.println("线程 "+Thread.currentThread().getName()+" 即将离开 ");
35                     sp.release();//释放
36                     System.out.println("线程 "+Thread.currentThread().getName()+" 已经离开"
37                             + " ,当前已有 "+(3-sp.availablePermits())+ " 个并发");
38                 }
39             });
40         }
41         service.shutdown();
42     }
43 }

执行结果如下:

线程 pool-1-thread-2 进入 ,当前已有 2 个并发
线程 pool-1-thread-3 进入 ,当前已有 3 个并发
线程 pool-1-thread-1 进入 ,当前已有 3 个并发
线程 pool-1-thread-3 即将离开 
线程 pool-1-thread-3 已经离开 ,当前已有 2 个并发
线程 pool-1-thread-4 进入 ,当前已有 3 个并发
线程 pool-1-thread-1 即将离开 
线程 pool-1-thread-5 进入 ,当前已有 3 个并发
线程 pool-1-thread-1 已经离开 ,当前已有 3 个并发
线程 pool-1-thread-2 即将离开 
线程 pool-1-thread-2 已经离开 ,当前已有 2 个并发
线程 pool-1-thread-6 进入 ,当前已有 3 个并发
线程 pool-1-thread-6 即将离开 
线程 pool-1-thread-6 已经离开 ,当前已有 2 个并发
线程 pool-1-thread-7 进入 ,当前已有 3 个并发
线程 pool-1-thread-7 即将离开 
线程 pool-1-thread-7 已经离开 ,当前已有 2 个并发
线程 pool-1-thread-8 进入 ,当前已有 3 个并发
线程 pool-1-thread-4 即将离开 
线程 pool-1-thread-4 已经离开 ,当前已有 2 个并发
线程 pool-1-thread-9 进入 ,当前已有 3 个并发
线程 pool-1-thread-5 即将离开 
线程 pool-1-thread-5 已经离开 ,当前已有 2 个并发
线程 pool-1-thread-10 进入 ,当前已有 3 个并发
线程 pool-1-thread-10 即将离开 
线程 pool-1-thread-10 已经离开 ,当前已有 2 个并发
线程 pool-1-thread-8 即将离开 
线程 pool-1-thread-8 已经离开 ,当前已有 1 个并发
线程 pool-1-thread-9 即将离开 
线程 pool-1-thread-9 已经离开 ,当前已有 0 个并发
View Code

Semaphore信号灯可以控制并发数,保证每次最多只能有三个线程在线程池中。

CyclicBarrier类的使用,可以模拟现实生活中的多人等待上车的情形,例如多人去旅行,那么当A到达集合点时,不能立即出发,必须等到B也到达集合点,那么A和B必须等到C也到达集合点,此时,三人可以坐车出发去下一站。该类就可以实现此功能,请看如下代码。

 1 public class CyclicBarrierTest
 2 {
 3     public static void main(String[] args)
 4     {
 5         //创建一个带有缓存的线程池
 6         ExecutorService service = Executors.newCachedThreadPool();
 7         //指定三个线程 只有当三个线程同时到达时 程序才会往下执行
 8         final CyclicBarrier cb = new CyclicBarrier(3);
 9         
10         for (int i = 0; i < 3; i++)
11         {
12             Runnable runnable = new Runnable()
13             {
14                 @Override
15                 public void run()
16                 {
17                     try
18                     {
19                         /**
20                          * cb.getNumberWaiting():从0开始,获取当前等待的线程数量
21                          */
22                         //第一个
23                         Thread.sleep(new Random().nextInt(1000));
24                         System.out.println("线程 "+Thread.currentThread().getName()+" 即将到达集合地点1,"
25                                 + "当前已有"+(cb.getNumberWaiting()+1)+" 个线程,"+(cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在继续等待"));
26                         cb.await();//让线程等待
27                         
28                         //第二个
29                         Thread.sleep(new Random().nextInt(1000));
30                         System.out.println("线程 "+Thread.currentThread().getName()+" 即将到达集合地点2,"
31                                 + "当前已有"+(cb.getNumberWaiting()+1)+" 个线程,"+(cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在继续等待"));
32                         cb.await();
33                         
34                         //第三个
35                         Thread.sleep(new Random().nextInt(1000));
36                         System.out.println("线程 "+Thread.currentThread().getName()+" 即将到达集合地点3,"
37                                 + "当前已有"+(cb.getNumberWaiting()+1)+" 个线程,"+(cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在继续等待"));
38                         cb.await();
39                     } catch (InterruptedException | BrokenBarrierException e)
40                     {
41                         e.printStackTrace();
42                     }
43                 }
44             };
45             service.execute(runnable);
46         }
47         
48         service.shutdown();//关闭线程池
49     }
50 }

如下是执行结果:

1 线程 pool-1-thread-2 即将到达集合地点1,当前已有1 个线程,正在继续等待
2 线程 pool-1-thread-1 即将到达集合地点1,当前已有2 个线程,正在继续等待
3 线程 pool-1-thread-3 即将到达集合地点1,当前已有3 个线程,都到齐了,继续走啊
4 线程 pool-1-thread-2 即将到达集合地点2,当前已有1 个线程,正在继续等待
5 线程 pool-1-thread-1 即将到达集合地点2,当前已有2 个线程,正在继续等待
6 线程 pool-1-thread-3 即将到达集合地点2,当前已有3 个线程,都到齐了,继续走啊
7 线程 pool-1-thread-1 即将到达集合地点3,当前已有1 个线程,正在继续等待
8 线程 pool-1-thread-2 即将到达集合地点3,当前已有2 个线程,正在继续等待
9 线程 pool-1-thread-3 即将到达集合地点3,当前已有3 个线程,都到齐了,继续走啊
View Code

CountDownLatch计数器的使用:

 * 演示一个计数器CountDownLatch

 * 模拟百米赛跑

 * 1个裁判 吹口哨

 * 3个运动员

 1 public class CountDownLatchTest
 2 {
 3     public static void main(String[] args)
 4     {
 5         //创建一个带有缓存的线程池
 6         ExecutorService service = Executors.newCachedThreadPool();
 7         final CountDownLatch cdOrder = new CountDownLatch(1);//裁判
 8         final CountDownLatch cdAnswer = new CountDownLatch(3);//运动员
 9         for (int i = 0; i < 3; i++)
10         {
11             Runnable runnable = new Runnable()
12             {
13                 @Override
14                 public void run()
15                 {
16                     try
17                     {
18                         System.out.println("线程"+Thread.currentThread().getName()+" 正准备接收命令 ");
19                         cdOrder.await();//等待计数器归0时 代码向下走
20                         System.out.println("线程"+Thread.currentThread().getName()+" 已接收命令 ");
21                         Thread.sleep(new Random().nextInt(1000));
22                         System.out.println("线程"+Thread.currentThread().getName()+" 回应命令处理结果  ");
23                         cdAnswer.countDown();//没调用一次该方法 就会将当前计数器上的计数减1
24                     } catch (InterruptedException e)
25                     {
26                         e.printStackTrace();
27                     }
28                 }
29             };
30             service.execute(runnable);
31         }
32         
33         
34         //主线恒
35         try
36         {
37             Thread.sleep(new Random().nextInt(1000));
38             System.out.println("线程"+Thread.currentThread().getName()+" 即将发布命令 ");
39             cdOrder.countDown();//相当于把计数器身上的计数减1
40             System.out.println("线程"+Thread.currentThread().getName()+" 已发送命令,正在等待结果   ");
41             cdAnswer.await();
42             System.out.println("线程"+Thread.currentThread().getName()+" 已收到所有相应结果");
43         } catch (InterruptedException e)
44         {
45             e.printStackTrace();
46         }
47         service.shutdown();//关闭线程池
48     }
49 }

如下是执行结果:

 1 线程pool-1-thread-1 正准备接收命令 
 2 线程pool-1-thread-2 正准备接收命令 
 3 线程pool-1-thread-3 正准备接收命令 
 4 线程main 即将发布命令 
 5 线程main 已发送命令,正在等待结果   
 6 线程pool-1-thread-1 已接收命令 
 7 线程pool-1-thread-2 已接收命令 
 8 线程pool-1-thread-3 已接收命令 
 9 线程pool-1-thread-2 回应命令处理结果  
10 线程pool-1-thread-3 回应命令处理结果  
11 线程pool-1-thread-1 回应命令处理结果  
12 线程main 已收到所有相应结果
View Code

Exchanger类可以实现两个线程之间的数据交换:

 1 public class ExchangerTest
 2 {
 3     public static void main(String[] args)
 4     {
 5         //创建一个带有缓存的线程池
 6         ExecutorService service = Executors.newCachedThreadPool();
 7         final Exchanger<String> changer = new Exchanger<String>();
 8         //开启第一个任务
 9         service.execute(new Runnable()
10         {
11             @Override
12             public void run()
13             {
14                 try
15                 {
16                     String dtail1 = "zhangsan";//准备要交换出去的数据
17                     System.out.println("线程 "+Thread.currentThread().getName()+" 正要把"+dtail1+"换出去");
18                     Thread.sleep(new Random().nextInt(1000));
19                     String dtail2 = changer.exchange(dtail1);//换回来的数据
20                     System.out.println("线程 "+Thread.currentThread().getName()+"换回的数据为"+dtail2);
21                 } catch (InterruptedException e)
22                 {
23                     e.printStackTrace();
24                 }
25             }
26         });
27         
28         //开启第二个任务
29         service.execute(new Runnable()
30         {
31             @Override
32             public void run()
33             {
34                 try
35                 {
36                     String dtail1 = "lisi";//准备要交换出去的数据
37                     System.out.println("线程 "+Thread.currentThread().getName()+" 正要把"+dtail1+"换出去");
38                     Thread.sleep(new Random().nextInt(1000));
39                     String dtail2 = changer.exchange(dtail1);//换回来的数据
40                     System.out.println("线程 "+Thread.currentThread().getName()+"换回的数据为"+dtail2);
41                 } catch (InterruptedException e)
42                 {
43                     e.printStackTrace();
44                 }
45             }
46         });
47         service.shutdown();
48     }
49 }

如下是执行结果:

1 线程 pool-1-thread-1 正要把zhangsan换出去
2 线程 pool-1-thread-2 正要把lisi换出去
3 线程 pool-1-thread-1换回的数据为lisi
4 线程 pool-1-thread-2换回的数据为zhangsan
View Code

以上都是java 5中的一些知识点,大家可以根据实际工作中的需要进行选择使用!!

推荐阅读