• 并发工具类:CountDownLatch、CyclicBarrier、Semaphore


    在多线程的场景下,有些并发流程需要人为来控制,在JDK的并发包里提供了几个并发工具类:CountDownLatch、CyclicBarrier、Semaphore。

    一、CountDownLatch

     1 import java.util.concurrent.CountDownLatch;
     2 
     3 
     4 public class CountDownLatchTest
     5 {
        //设置N为2
    6 static CountDownLatch c = new CountDownLatch(2); 7 public static void main(String[] args) throws Exception 8 { 9 Thread t1 = new Thread(new Runnable() 10 { 11 12 @Override 13 public void run() 14 { 15 System.out.println("1");
               //将N减1
    16 c.countDown(); 17 } 18 }); 19 20 Thread t2 = new Thread(new Runnable() 21 { 22 23 @Override 24 public void run() 25 { 26 System.out.println("2"); 27 c.countDown(); 28 } 29 }); 30 t1.start(); 31 t2.start();
          //让当前线程等待,直到计数N为0
    32 c.await(); 33 System.out.println("3"); 34 } 35 }

    执行结果:

    1
    2
    3

    这里会存在两种结果:123或者213,但是绝对不会出现3打印在1、2前面的。

    new CountDownLatch(2);

    这个CountDownLatch的构造函数接收一个int类型的参数作为计数器,N表示阻塞的线程必须等待N次countDown才能执行。

    每次调用CountDownLatch的countDown方法时,N就会减1,而这个方法可以使用在任何地方,这里的N点可以是N个线程,也可以是一个线程中N个步骤。

    而CountDownLatch的await方法则会阻塞当前线程,直到N为0的时候才能执行。

    我们将上面的程序改造下,让线程中有两个打印动作,并且第二个动作前线程休眠一段时间:

     1 import java.util.concurrent.CountDownLatch;
     2 
     3 
     4 public class CountDownLatchTest
     5 {
     6     static CountDownLatch c = new CountDownLatch(2);
     7     public static void main(String[] args) throws Exception
     8     {
     9         Thread t1 = new Thread(new Runnable()
    10         {
    11             
    12             @Override
    13             public void run()
    14             {
    15                 System.out.println("1");
    16                 c.countDown();
    17                 try
    18                 {
    19                     Thread.sleep(500);
    20                     System.out.println("2");
    21                 }
    22                 catch (InterruptedException e)
    23                 {
    24                     e.printStackTrace();
    25                 }
    26             }
    27         });
    28         
    29         Thread t2 = new Thread(new Runnable()
    30         {
    31             
    32             @Override
    33             public void run()
    34             {
    35                 System.out.println("3");
    36                 c.countDown();
    37             }
    38         });
    39         t1.start();
    40         t2.start();
    41         c.await();
    42         System.out.println("4");
    43     }
    44 }

    执行结果:

    1
    3
    4
    2

    这个结果是由于在打印完1、3之后,N已经变化为0,主线程执行打印4,由于线程1休眠,所以2最后才打印。

    在上面的程序中,如果将N设置为3,则主线程中的打印4永远不会执行,因为没有N永远只会到1而不会减少到0.

    在这里我们想起了线程的join方法,这个方法也是可以阻塞当前线程,等待某线程执行完成。通过对比,我们可以发现使用CountDownLatch这个工具类更灵活,因为countDown可以用在任何线程的任何地方。

    CountDownLatch适合一个大任务拆分成多个小任务,然后在所有子任务完成后,通知其他的后续操作开始执行。

    二、同步屏障CyclicBarrier

    CyclicBarrier默认的构造方法CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达屏障,然后当前线程被阻塞,直到被拦截的线程全部都到达了屏障,然后前面被阻塞的线程才能开始执行,否则会被一直阻塞。

     1 public class CyclicBarrierTest
     2 {
     3     static CyclicBarrier c = new CyclicBarrier(3);
     4     
     5     public static void main(String[] args)
     6         throws Throwable, BrokenBarrierException
     7     {
     8         Thread t1 = new Thread(new Runnable()
     9         {
    10             @Override
    11             public void run()
    12             {
    13                 try
    14                 {
    15                     c.await();
    16                 }
    17                 catch (InterruptedException e)
    18                 {
    19                     e.printStackTrace();
    20                 }
    21                 catch (BrokenBarrierException e)
    22                 {
    23                     e.printStackTrace();
    24                 }
    25                 System.out.println("1");
    26                 
    27             }
    28         });
    29         Thread t2 = new Thread(new Runnable()
    30         {
    31             @Override
    32             public void run()
    33             {
    34                 try
    35                 {
    36                     c.await();
    37                 }
    38                 catch (InterruptedException e)
    39                 {
    40                     e.printStackTrace();
    41                 }
    42                 catch (BrokenBarrierException e)
    43                 {
    44                     e.printStackTrace();
    45                 }
    46                 System.out.println("2");
    47             }
    48         });
    49         
    50         t1.start();
    51         t2.start();
    52         c.await();
    53         System.out.println("3");
    54     }
    55 }

    执行结果:

    3
    1
    2

    上述中被屏障拦截的线程有3个,其中线程1和线程2执行的时候先到达屏障,然后被阻塞,主线程执行第52行到达屏障,至此阻塞的三个线程全部到达屏障,然后阻塞的线程可以去竞争CPU开始执行。

    如果将拦截的线程数修改为4:

    static CyclicBarrier c = new CyclicBarrier(4);

    这样的话被拦截的线程数有4个,但是只有三个线程调用await方法告诉CyclicBarrier,我到达了屏障。所以这三个线程都会被阻塞。

    另外还有一点就是CyclicBarrier的计数器可以重置,例如设置的是拦截线程数量为2,但是有3个线程调用了await()方法表示到达了屏障,这个时候会出现最先达到屏障的两个线程顺利执行完毕,而最后到达的第三个线程则一直被阻塞,因为它等不到另外一个线程到达屏障了。

    而如果拦截线程的数量依旧为2,但是有4个线程调用了await()方法,那么这4个线程是分两批执行的,前两个线程满足拦截的线程数,到达屏障后放行;然后CyclicBarrier的计数器重置,后面两个线程到达屏障后放行。

     1 import java.util.concurrent.BrokenBarrierException;
     2 import java.util.concurrent.CyclicBarrier;
     3 
     4 import sun.java2d.SunGraphicsEnvironment.T1Filter;
     5 
     6 
     7 public class CyclicBarrierTest
     8 {
     9     static CyclicBarrier c = new CyclicBarrier(2);
    10     
    11     public static void main(String[] args) throws Throwable, BrokenBarrierException
    12     {
    13         Thread t1 = new Thread(new Runnable()
    14         {
    15             @Override
    16             public void run()
    17             {
    18                 try
    19                 {
    20                     c.await();
    21                 }
    22                 catch (InterruptedException e)
    23                 {
    24                     e.printStackTrace();
    25                 }
    26                 catch (BrokenBarrierException e)
    27                 {
    28                     e.printStackTrace();
    29                 }
    30                 System.out.println("1");
    31                 
    32             }
    33         });
    34         Thread t2 = new Thread(new Runnable()
    35         {
    36             @Override
    37             public void run()
    38             {
    39                 try
    40                 {
    41                     c.await();
    42                 }
    43                 catch (InterruptedException e)
    44                 {
    45                     e.printStackTrace();
    46                 }
    47                 catch (BrokenBarrierException e)
    48                 {
    49                     e.printStackTrace();
    50                 }
    51                 System.out.println("2");
    52             }
    53         });
    54         
    55         Thread t3 = new Thread(new Runnable()
    56         {
    57             @Override
    58             public void run()
    59             {
    60                 try
    61                 {
    62                     c.await();
    63                 }
    64                 catch (InterruptedException e)
    65                 {
    66                     e.printStackTrace();
    67                 }
    68                 catch (BrokenBarrierException e)
    69                 {
    70                     e.printStackTrace();
    71                 }
    72                 System.out.println("3");
    73                 
    74             }
    75         });
    76         t1.start();
    77         t2.start();
    78         t3.start();
    79         c.await();
    80         System.out.println("4");
    81     }
    82 }

    CyclicBarrier可以用于多线程计算数据,最后合并结果的场景;由于CyclicBarrier的计数器可以重置,所以可以使用它处理更为复杂的业务场景,而CountDownLatch计数器只能使用一次。

    三、信号量Semaphore

    无论是内部锁synchronized还是重入锁ReentrantLock,一次都只允许一个线程访问某一个资源,而信号量却可以指定多个线程同时访问某一资源;主要用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。

     1 public class SemaphoreTest implements Runnable
     2 {
     3     final Semaphore s = new Semaphore(5);
     4     @Override
     5     public void run()
     6     {
     7         try
     8         {
     9             s.acquire();
    10             Thread.sleep(1000);
    11             System.out.println(Thread.currentThread().getName() + " is done");
    12             s.release();
    13         }
    14         catch (InterruptedException e)
    15         {
    16             e.printStackTrace();
    17         }
    18     }
    19 }
    public class MainTest
    {
        public static void main(String[] args)
        {
            SemaphoreTest s = new SemaphoreTest();
            //创建一个可重用固定线程数的线程池,线程数量为20
            ExecutorService threadPool= Executors.newFixedThreadPool(20);
            for(int i=0;i<20;i++)
            {
                threadPool.submit(s);
            }
            threadPool.shutdown();
        }
    }

    执行的结果是每五个线程为一组打印消息。

    线程池里面有20个可重复使用的线程数量,但是信号量只有5个,也就是每次只能并发5个线程执行,其他线程阻塞。

    信号量为5,可以认为线程池里有5把锁,每个线程调用acquire和release分别表示获取锁和释放锁,这样,通过信号量就可以调度多个线程的执行。

  • 相关阅读:
    Zookeeper----1.基础知识
    UML图
    VUE入门3---axios
    VUE入门2---vue指令
    谁先执行?props还是data或是其他? vue组件初始化的执行顺序详解
    vue双向绑定原理分析
    HTML/CSS -- 浏览器渲染机制
    vue工作原理分析
    导入导出需求整理
    .NET 异步详解
  • 原文地址:https://www.cnblogs.com/dongguacai/p/6023028.html
Copyright © 2020-2023  润新知