Java并发编程之常用的辅助类详解

目录
  • 1.CountDownLatch
    •  1.2.示例:班长锁门问题
    • 1.2.CountDownLatch类简介:
      • 1.2.1 CountDownLatch概念
      • 1.2.3 CountDownLatch的用法
    • 1.3.CountDownLatch案例:
      • 1.4.原理总结
      • 2.CyclicBarrier
        • 2.1.CyclicBarrier简介
          • 2.2.案例:集齐7颗龙珠召唤神龙
          • 3.Semophore
            •  3.1.Semophore简介
              • 3.2.抢车位问题
                • 3.3.原理总结
                • 总结

                  1.CountDownLatch

                   1.2.示例:班长锁门问题

                  问题描述:假如有7个同学晚上上自习,钥匙在班长手上,并且要负责锁门。班长必须要等所有人都走光了,班长才能关灯锁门。这6个同学的顺序是无序的,不知道它们是何时离开。6个同学各上各的自习,中间没有交互。假如说6个学生是普通线程,班长是主线程,如何让主线程要等一堆线程运行完了,主线程才能运行完成呢。

                  public class CountDownLatchDemo {
                  	public static void main(String[] args) {
                  		// TODO Auto-generated method stub
                  		for(int i=1;i<=6;i++){
                  			new Thread(()->{
                  				System.out.println(Thread.currentThread().getName()+"\t离开教室");
                  			},String.valueOf(i)).start();
                  		}
                  		System.out.println(Thread.currentThread().getName()+"\t班长关门走人");
                  	}
                  }
                  

                  运行结果截图

                  最后还有三个人被锁在教师了,这样可能会发生事故,所以肯定不行的。

                  我们要想实现这样的效果,就是等其它线程全部走完了,主线程才能运行。就需要借助JUC中的CountDownLatch类

                  1.2.CountDownLatch类简介:

                  1.2.1 CountDownLatch概念

                  CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。

                  CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一。当计数器的值为0时,表示所有的线程都已经完成一些任务,然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务

                  CountDownLatch说明:count计数,down倒计算,Latch开始

                  1.2.3 CountDownLatch的用法

                  某一线程在开始运行前等待n个线程执行完毕。将CountDownLatch的计数器初始化为new CountDownLatch(n),每当一个任务线程执行完毕,就将计数器减1 countdownLatch.countDown(),当计数器的值变为0时,在CountDownLatch上await()的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。

                  CountDownLatch底层构造函数源代码

                  public CountDownLatch(int count) {
                   if (count < 0) throw new IllegalArgumentException("count < 0");
                   this.sync = new Sync(count);
                   }
                  

                  1.3.CountDownLatch案例:

                  public static void main(String[] args) throws InterruptedException {
                  		//6个同学正在上自习,每个人就有一个1计数器,走1个数字减1,main线程启动,必须要等计时器从6变成0,才能开始。
                  		CountDownLatch countDownLatch=new CountDownLatch(6);
                  		for(int i=1;i<=6;i++){
                  			new Thread(()->{
                  				System.out.println(Thread.currentThread().getName()+"\t离开教室");
                  				countDownLatch.countDown();		//计算减少一个
                  			},String.valueOf(i)).start();
                  		}
                  		countDownLatch.await();	//班长前面需要被阻塞
                  		System.out.println(Thread.currentThread().getName()+"\t班长关门走人");
                  	}
                  

                  运行结果截图

                  这里每个人何时走并不知道, 但是可以保证每次都是班长最后一个走。

                  1.4.原理总结

                  CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会被阻塞。

                  其它线程调用countDown方法将会使计数器减1(调用countDown方法的线程不会阻塞)

                  当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行。

                  2.CyclicBarrier

                  2.1.CyclicBarrier简介

                  cyclic循环,barrier屏障。

                  从字面上的意思可以知道,这个类的中文意思是“循环栅栏”。大概的意思就是一个可循环利用的屏障。

                  它的作用就是会让所有线程都等待完成后才会继续下一步行动。

                  上面班长关门的例子是做倒计时,这里是反过来,做加法,数到多少就开始。

                  比如人到齐了,再开会。,举个例子,就像生活中我们会约同事一起去开会,有些同事可能会早到,有些同事可能会晚到,但是这个会议规定必须等到所有人到齐之后才会让我们正式开会。这里的同事们就是各个线程,会议就是 CyclicBarrier。

                  构造方法

                  public CyclicBarrier(int parties)
                  public CyclicBarrier(int parties, Runnable barrierAction)
                  

                  解析:

                  parties 是参与线程的个数

                  第二个构造方法有一个 Runnable 参数,这个参数的意思是最后一个到达线程要做的任务

                  我们通常用第二个构造函数。

                  2.2.案例:集齐7颗龙珠召唤神龙

                  public static void main(String[] args) {
                  		// TODO Auto-generated method stub
                  		CyclicBarrier cyclicBarrier=new CyclicBarrier(7,()->{System.out.println("召唤神龙");});
                  		for(int i=1;i<=7;i++){
                  			final int tempInt=i;
                  			new Thread(()->{
                  				System.out.println(Thread.currentThread().getName()+"\t收集到第"+tempInt+"颗龙珠");
                  				try {
                  					//某个线程收集到了龙珠只能先等着,等龙珠收齐了才能召唤神龙
                  					cyclicBarrier.await();
                  				} catch (Exception e) {
                  					// TODO Auto-generated catch block
                  					e.printStackTrace();
                  				}
                  			},String.valueOf(i)).start();;
                  		}
                  	}
                  

                  截图

                  3.Semophore

                   3.1.Semophore简介

                  前面讨论的问题都是多对一的问题,我们现在可以讨论多对多的问题了。

                  假设有7个兄弟开车上班,而现在只有4个车位。7部车并列开进4个车位,每个车停了多长时间未知,资源被占用完了。假设有一个车只停了2s,那么它走了,外面的车又可以进来了。走一个进一个,最后全部都可以进去。而semophore就是控制多线程的并发策略。

                  简单理解来说,Semaphore:信号量主要用于两个目的:一个是用于多个共享资源的互斥使用;另一个用于并发线程数量的控制。

                  Semaphore类有两个重要方法

                  1、semaphore.acquire();
                  请求一个信号量,这时候信号量个数-1,当减少到0的时候,下一次acquire不会再执行,只有当执行一个release()的时候,信号量不为0的时候才可以继续执行acquire

                  2、semaphore.release();
                  释放一个信号量,这时候信号量个数+1,

                  3.2.抢车位问题

                  public static void main(String[] args) {
                  		//模拟6部车抢3个空车位
                  		Semaphore semaphore=new Semaphore(3);//模拟资源类,有3个空车位
                  		for(int i=1;i<=6;i++){
                  			new Thread(()->{
                  				try {
                  					//谁先抢到了,谁就占一个车位,并且要把semaphore中的资源数减1
                  					semaphore.acquire();
                  					System.out.println(Thread.currentThread().getName()+"\t抢占到了车位");
                  					TimeUnit.SECONDS.sleep(3);
                  					System.out.println(Thread.currentThread().getName()+"\t离开了车位");
                  					
                  				} catch (Exception e) {
                  					// TODO Auto-generated catch block
                  					e.printStackTrace();
                  				}finally{
                  					//释放车位
                  					semaphore.release();
                  				}
                  				
                  			},String.valueOf(i)).start();
                  		}
                  	}
                  

                  运行结果截图:

                  3.3.原理总结

                  在信号量上我们定义两种操作:

                  acquire(获取)当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1),要么一直等待下去,直到有线程释放信号量,或超时。

                  release(释放)实际上会将信号量的值加1,然后唤醒等待的线程。

                  信号量主要用于两个目的:一个是用于多个共享资源的互斥使用;另一个用于并发线程数量的控制

                  如果把资源数从3变成1了,此时就等价于synchronized。

                  总结

                  本文转自网络,如有侵权请联系客服删除。