Java并发学习(3)一5种同步辅助类

前言


线程之前通常需要进一步的协调工作,来完成相应比较复杂的并发任务,使用wait/notify等方法都是底层实现。现在我们需要的更为抽象满足具体实际业务的一些方法,这时候我们就会用到相应的5种同步辅助类。
主要参考资料:这里写链接内容

  • CountDownLatch:它允许一个或多个线程一直等待,直到其他线程执行完后再执行。利用它可以实现类似于计数器的功能。
  • Semaphore:信号量,如果学过操作系统中PV操作的话,对这个应该很熟悉。是表示当前可用的资源数量。
  • CyclicBarrier:回环栅栏(翻译:可循环使用的屏障),作用是将一组线程到达一种状态(或者屏障,也可以叫同步点)时等待阻塞,直到最后一个到达屏障(同步点)时,屏障才会打开。所有的线程才能进行执行。
  • Phaser:一张可重用的同步屏障,作用是在线程的阶段都对线程进行同步,等所有的线程完成这一步后才能继续进行下一步。
  • Exchanger:提供一个同步点,在这个同步点。一对线程相互比较彼此的数据。

一、CountDownLatch

//唯一的构造方法
public CountDownLatch(int count) {  };  //参数count为计数值

//CountDownLatch的三个方法
public void await() throws InterruptedException { };   //调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  //和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public void countDown() { };  //将count值减1

比如:有主线程任务A,它需要等待其他两个线程任务B与C执行完毕后才能执行

  • 则线程任务B与C分别调用CountDownLatchcountDown方法时,count就会减一,直至减为零。

  • 主线程任务A使用await方法等待,当count的值变为零,执行await的主线程A继续执行。

实现代码:

public class CountDownLatchDemo {

    public static void main(String[] args) {
        CountDownLatch latch = new CountDownLatch(2);//计数数count=2
        //线程任务B
        new Thread(){
            public void run() {
                try {
                    System.out.println("子线程任务B:"+Thread.currentThread().getName()+"正在执行");
                   Thread.sleep(3000);
                   System.out.println("子线程任务B:"+Thread.currentThread().getName()+"执行完毕");
                   latch.countDown();//count--
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
            };
        }.start();
        //线程任务C
        new Thread(){
            public void run() {
                try {
                    System.out.println("子线程任务C: "+Thread.currentThread().getName()+"正在执行");
                    Thread.sleep(3000);
                    System.out.println("子线程任务C:"+Thread.currentThread().getName()+"执行完毕");
                    latch.countDown();//count--
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
            };
        }.start();
        //主线程任务A
        new Thread(){
            public void run() {
                try {
                    latch.await();//等待两个任务BC完毕
                    System.out.println("主线程任务A:"+Thread.currentThread().getName()+"正在执行");
                    Thread.sleep(3000);
                    System.out.println("主线程任务A:"+Thread.currentThread().getName()+"执行完毕");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
        }.start();

    }
}

当我们不使用await()方法时:主线程A与线程B、C之间执行顺序是不固定的,不会等待BC完毕后才执行
这里写图片描述
使用await()方法之后,线程A总会等待B、C执行完毕之后才执行。
这里写图片描述

join()方法的区别:

  • join()方法是,不停检查thread是否存活,如果存活则让当前线程永远wait,直到thread线程终止,线程的this.notifyAll 就会被调用。
  • CountDownLatch是一直检测计数器count是否为0,不为0则一直阻塞,当计数器的值为0时,因调用await()方法被阻塞的线程会被唤醒,继续执行。

二、CyclicBarrier

CyclicBarrier有两个构造器:

//parties: 表示让多少个线程等待到barrier/同步点;
//barrierAction: 表示线程到达barrier状态之后要执行的动作。
public CyclicBarrier(int parties, Runnable barrierAction) {}
public CyclicBarrier(int parties) {}

注意:barrierAction是优先于await()后面的方法的,即线程达到同步点之后,先执行barrierAction后执行await()后面的方法。
CycilcBarrier有两个await()方法:跟CountDownLatch的的两个await()类似,但主要不是等待count为0,而是等待线程是否到达barrier状态。

public int await() throws InterruptedException, BrokenBarrierException { };
public int await(long timeout, TimeUnit unit)throws InterruptedException,BrokenBarrierException,TimeoutException { 

比如:现在有四个线程,每个线程都分为两个阶段,一个执行写操作,一个是执行读操作。当前我们需要4个线程都执行完毕写操作(达到写状态)之后,随机选一个线程再进行读操作。

public class CyclicBarrierDemo {

      public static void main(String[] args) {
            int N = 4;
            CyclicBarrier barrier  = new CyclicBarrier(N,new Runnable() {
                @Override
                public void run() {
                    System.out.println("所有写操作执行完毕,下面继续执行读操作...");
                System.out.println("线程"+Thread.currentThread().getName()+"正在读数据...");
            }
        });
        System.out.println("所有线程开始执行写操作...");
        for(int i=0;i<N;i++)
            new Writer(barrier).start();
    }
    static class Writer extends Thread{
        private CyclicBarrier cyclicBarrier;
        public Writer(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }

        @Override
        public void run() {
            try {
                System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据...");
                Thread.sleep(3000);    
                System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕");
                cyclicBarrier.await();//所有线程等待达到barrier屏障(写操作完毕)之后被阻塞
            } catch (InterruptedException e) {
                e.printStackTrace();
            }catch(BrokenBarrierException e){
                e.printStackTrace();
            }
        }
    }
}

执行结果如下:当0-3线程中任意一个线程到达写状态之后,开始等待其他的线程完成写操作。当线程0-3全部执行完写操作后,随机选取一个线程就开始执行读操作。
这里写图片描述

CyclicBarrier的重用:CyclicBarrier到达屏障点之后还可以进行再一次的重复使用,而CountDownLatch是不能的。

 System.out.println("所有线程开始执行写操作...");
     for(int i=0;i<N;i++)
          new Writer(barrier).start();
 try {
          Thread.sleep(3000);
     } catch (InterruptedException e) {
         e.printStackTrace();
  }

  System.out.println("CyclicBarrier重用...");
       for(int i=0;i<N;i++) {
           new Writer(barrier).start();
   }

0-3线程达到barrier之后,CyclicBarrier重用创建3-7线程执行相同的任务:
这里写图片描述

三、Semaphore

Semaphore表示线程公共申请的资源个数,可以控制同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。

Semaphore构造方法:

//参数permits表示许可数目,即同时可以允许多少线程进行访问
public Semaphore(int permits) {          
    sync = new NonfairSync(permits);
}
//这个多了一个参数fair表示是否是公平的,即等待时间越久的越先获取许可
public Semaphore(int permits, boolean fair) {    
    sync = (fair)? new FairSync(permits) : new NonfairSync(permits);
}

Semaphore的几个重要的方法:acquire()、release()、tryAcquire()、availablePermits()、availablePermits()方法:

(1)acquire()release()方法:获得、释放许可。相当于PV操作中的P(S)、V(S)操作

public void acquire() throws InterruptedException {  }     //获取一个许可
public void acquire(int permits) throws InterruptedException { }    //获取permits个许可
public void release() { }          //释放一个许可
public void release(int permits) { }    //释放permits个许可

(2)tryAcquire()方法:尝试获得许可,在release()方法释放之前,必须获得许可:

public boolean tryAcquire() { };    //尝试获取一个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException { };  //尝试获取一个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false
public boolean tryAcquire(int permits) { }; //尝试获取permits个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException { }; //尝试获取permits个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false

(3)availablePermits()方法得到可用的许可数目:

public int availablePermits() {
    return sync.getPermits();
}

比如:我们有10个人,但是只有5台打印机,一台打印机只能被一个人占用。只有打印机使用结束后,下一个人才会占用该打印机。此时的资源为5台打印机,也就Semaphore是允许最多的并发线程数目permits为5。

public class SemaphoreDemo {

    private static final int THREAD_COUNT = 10;  //10个线程
    private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);  //获得线程池
    private static Semaphore semaphore = new Semaphore(5);  //只允许最高5个线程并发

    public static void main(String[] args) {
        for (int i = 0; i < THREAD_COUNT; i++) {
            print(i);
        }
        threadPool.shutdown();
    }

    public static void print(int num) {
        threadPool.execute(new Runnable() {
            public void run() {
                try {
                    semaphore.acquire();//默认5个线程一起运行,即每次获取5个permits
                    System.out.println("员工"+num+"占用一台打印机正在打印...");
                    Thread.sleep(3000);
                    System.out.println("员工"+num+"释放出打印机");
                    semaphore.release();   
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

这里写图片描述

四、Exchanger

提供一个同步点,在这同步点两个线程进行交换数据,如果其中一个先执行exchange()方法,它就会一直等待第二个线程也执行exchange()方法。两个同时到达同步点之后就可以交换数据,但需要注意的是该线程只能是两个线程之间的交换数据,并不支持更多的线程。
比如:我们可以用来对比数据,线程A传来数据1,线程B传来数据2,我们需要比较数据1跟数据2。如果相等则可以认为是有效数据,进行入库操作;不相等则是无效数据。

public class ExchangerDemo {


    private static final Exchanger<String> exchanger = new Exchanger<String>();  
    private static ExecutorService threadPool = Executors.newFixedThreadPool(2);  

    public static void main(String[] args) {

         //线程A
         threadPool.execute(new Runnable() {  
                @Override  
                public void run() {  
                    try {  
                        String A = "data1";// 线程A传入数据1
                        String B=exchanger.exchange(A);  //如果线程A先执行exchange方法,则线程A等待阻塞。直到线程B也执行exchange方法
                        System.out.println("我是A线程,这是我的视角:我传入的数据是"+A+ ", 而B线程传入的是"+B);  
                    } catch (InterruptedException e) {  
                    }  
                }  
            });
            //线程B  
            threadPool.execute(new Runnable() {  
                @Override  
                public void run() {  
                    try {  
                        String B = "data2";// 线程B传入数据2  
                        String A = exchanger.exchange(B);  
                        System.out.println("我是B线程,这是我的视角:我传入的数据是"+B+", 而A线程传入的是"+A);  
                    } catch (InterruptedException e) {  
                    }  
                }  
            });  
            threadPool.shutdown();  
        }  

}

线程AB所传入的数据是不可见的,但是在执行exchange()方法之后,线程AB进行了沟通,将彼此的数据进行了交换。
这里写图片描述

五、Phaser

  • Phaser(int parties),构造方法,与CountDownLatch一样,传入同步的线程数。
  • register()动态添加一个或多个参与者
  • getPhase()用于获取当前的阶段.默认阶段从0开始.
  • arriveAndDeregister()方法,动态撤销线程在phaser的注册,通知phaser对象,该线程已经结束该阶段且不参与后面阶段。
  • arriveAndAwaitAdvance()方法,类似await()方法,记录到达线程数,阻塞等待其他线程到达同步点后再继续执行。
  • onAdvance(int phase,int registeredParties):这个方法比较特殊,表示当进入下一个phase时可以进行的事件处理,如果返回true表示此Phaser应该终止(此后将会把Phaser的状态为termination,即isTermination()将返回true。),否则可以继续进行。类似于相当于CyclicBarrier的barrierAction方法。phase参数表示当前周期数,registeredParties表示当前已经注册的parties个数。
public class PhaserDemo {

    private static final int THREAD_COUNT = 5;  //5个线程
    private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);  //获得线程池
    private static final int parties = 3;//三个阶段

    public static void main(String[] args) {

        final Phaser phaser = new Phaser(parties); 
        for (int i = 0; i < THREAD_COUNT; i++) {
            phaser.register(); //每个线程注册一个phaser
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                     try {
                            while (!phaser.isTerminated()) {
                                //第一阶段
                                System.out.println(Thread.currentThread().getName()+"到达第一阶段:准备阶段");  
                                phaser.arriveAndAwaitAdvance();//所有线程到达第一阶段阻塞
                                //第二阶段
                                System.out.println(Thread.currentThread().getName()+"到达第二阶段:计算阶段");  
                                phaser.arriveAndAwaitAdvance();  
                                //第三阶段
                                System.out.println(Thread.currentThread().getName()+"到达第三阶段:归档阶段");  
                                phaser.arriveAndDeregister();  //所有阶段完毕后线程注销
                            } 
                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                }
            });
        }
        threadPool.shutdown();

    }
}

猜你喜欢

转载自blog.csdn.net/mynewclass/article/details/80418174