java线程六之闭锁,栅栏与计数信号量

先直接上各个的示例,再看各自的区别。

1.闭锁,等待锁的计数器为0才会执行,否则会一直等待,示例如下:

/**
 * Created by 
 * Date : 2018/7/19 10:33
 * 闭锁
 */
public class Main {
    private CountDownLatch countDownLatch=new CountDownLatch(3);
    public static void main(String[] args)throws InterruptedException{
        Main m=new Main();
        m.test();

        TimeUnit.SECONDS.sleep(2);
        m.oper();
    }
    public void test(){
        for(int i=1;i<=2;i++){
            new Thread(String.valueOf(i)){
                public void run(){
                    System.out.println("线程"+Thread.currentThread().getName()+"执行");
                    countDownLatch.countDown();
                }
            }.start();
        }
    }
    public void oper(){
        System.out.println("等待所有线程执行完毕");
        try{
            countDownLatch.await();
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("线程全都执行完了,执行oper");
    }
}

执行结果:

线程1执行
线程2执行
等待所有线程执行完毕

从结果可以看出来,countDownLatch锁的计数器为3,但是仅仅通过countDownLatch.countDown()执行了两次,这个方法的作用是将计数器减1,所以countDownLatch锁的计数器变为1,不为0,当执行countDownLatch.await()时,就会一直等待。

我们将代码稍微改改,在countDownLatch.await()上再执行一次countDownLatch.countDown(),oper()方法代码如下:

public void oper(){
    System.out.println("等待所有线程执行完毕");
    try{
        countDownLatch.countDown();
        countDownLatch.await();
    }catch (Exception e){
        e.printStackTrace();
    }
    System.out.println("线程全都执行完了,执行oper");
}

执行结果:

线程1执行
线程2执行
等待所有线程执行完毕
线程全都执行完了,执行oper

我们可以看到,当再减去一次后,计数器变为了0,countDownLatch.await()就不再阻塞了,执行了后面的内容。这儿的countDownLatch.countDown()也可以放在另外一个线程中,等待另外一个线程执行完毕。

因此,在并发程序中,闭锁主要运用于:“等待其它线程都执行完毕==》再执行”的此类的业务流程。

上述代码,如果超过了计数器数量会怎么样?我们看看代码:

/**
 * Created by WangZhiXin
 * Date : 2018/7/19 10:33
 * 闭锁
 */
public class Main {
    private CountDownLatch countDownLatch=new CountDownLatch(3);
    public static void main(String[] args)throws InterruptedException{
        Main m=new Main();
        m.test();

        TimeUnit.SECONDS.sleep(2);
        m.oper();
    }
    public void test(){
        for(int i=1;i<=6;i++){
            new Thread(String.valueOf(i)){
                public void run(){
                    System.out.println("线程"+Thread.currentThread().getName()+"执行");
                    countDownLatch.countDown();
                }
            }.start();
        }
    }
    public void oper(){
        System.out.println("等待所有线程执行完毕");
        try{
            countDownLatch.await();
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("线程全都执行完了,执行oper");
    }
}

执行结果:

线程1执行
线程3执行
线程5执行
线程4执行
线程2执行
线程6执行
等待所有线程执行完毕
线程全都执行完了,执行oper

上述程序,计数器减了6次,通过结果我们看到了,闭锁等待并执行,只会执行一次。

2.栅栏,等待锁的计数器全部等待到达,才会执行,示例如下:

/**
 * Created by 
 * Date : 2018/7/19 10:43
 * 栅栏
 */
public class Main {
    private CyclicBarrier cyclicBarrier=new CyclicBarrier(3);
    public static void main(String[] args)throws InterruptedException{
        Main m=new Main();
        m.test();
    }
    public void test(){
        for(int i=1;i<=2;i++){
            new Thread(String.valueOf(i)){
                public void run(){
                    System.out.println("线程"+Thread.currentThread().getName()+"执行");
                    try{
                        cyclicBarrier.await();
                        System.out.println("线程"+Thread.currentThread().getName()+"执行完毕,congratulations");
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }.start();
        }
    }
}

执行结果:

线程1执行
线程2执行

从结果看出来,锁的计数器为3.但是只有两个线程等待到达了,还差一个计数器等待到达,所以程序不会往后执行,会一直阻塞在这儿。

如果再加一个线程,让锁计数器满足三个等待到达的情况下,我们看看结果:

/**
 * Created by 
 * Date : 2018/7/19 10:43
 * 栅栏
 */
public class Main {
    private CyclicBarrier cyclicBarrier=new CyclicBarrier(3);
    public static void main(String[] args)throws InterruptedException{
        Main m=new Main();
        m.test();
    }
    public void test(){
        for(int i=1;i<=3;i++){
            new Thread(String.valueOf(i)){
                public void run(){
                    System.out.println("线程"+Thread.currentThread().getName()+"执行");
                    try{
                        cyclicBarrier.await();
                        System.out.println("线程"+Thread.currentThread().getName()+"执行完毕,congratulations");
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }.start();
        }
    }
}

执行结果:

线程1执行
线程3执行
线程2执行
线程2执行完毕,congratulations
线程3执行完毕,congratulations
线程1执行完毕,congratulations

我们可以看到,三个线程被阻塞后分别各自等待其它的线程等待到达,便继续执行代码了。

因此,在并发程序中,栅栏主要运用于“执行程序==》阻塞等待其它程序到达==》再次执行阻塞后的程序”此类的业务流程。

上述代码如果也超过了计数器的数量会怎么样?我们也看看代码:

/**
 * Created by WangZhiXin
 * Date : 2018/7/19 10:43
 * 栅栏
 */
public class Main {
    private CyclicBarrier cyclicBarrier=new CyclicBarrier(3);
    public static void main(String[] args)throws InterruptedException{
        Main m=new Main();
        m.test();
    }
    public void test(){
        for(int i=1;i<=6;i++){
            new Thread(String.valueOf(i)){
                public void run(){
                    System.out.println("线程"+Thread.currentThread().getName()+"执行");
                    try{
                        cyclicBarrier.await();
                        System.out.println("线程"+Thread.currentThread().getName()+"执行完毕,congratulations");
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }.start();
        }
    }
}

执行结果:

线程2执行
线程3执行
线程1执行
线程4执行
线程6执行
线程1执行完毕,congratulations
线程3执行完毕,congratulations
线程2执行完毕,congratulations
线程5执行
线程5执行完毕,congratulations
线程4执行完毕,congratulations
线程6执行完毕,congratulations

上述程序等待到达了6次,我们看到结果,到达3次后,执行了线程1,2,3的后面代码,再继续到达3次后,执行了线程4,5,6的后面代码。通过这儿也可以看出,栅栏和闭锁的区别,等到该篇文章后面也会做总结。

3.计数信号量,方法加上synchronized同步,那么对同一个锁对象的情况下,这个方法只能一个一个的同步执行。而计数信号量的产生,是为了一批一批的执行,并且控制执行的数量。当然极端下的计数信号量,如果参数为1的话,那么它相当于synchronized,一个一个的执行。

老规矩,还是看示例:

/**
 * Created by 
 * Date : 2018/7/19 11:34
 * 计数信号量
 */
public class Main {
    private Semaphore semp = new Semaphore(5);//获取计数信号量,按5个一批执行
    public static void main(String[] args){
        Main m=new Main();
        m.test();
    }

    public void test(){
        for(int i=1;i<=20;i++){
            new Thread(String.valueOf(i)){
                public void run(){
                    try{
                        semp.acquire();//获取许可执行
                        System.out.println("线程"+Thread.currentThread().getName()+"执行");
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }finally {
//                        semp.release();//释放许可
//                        System.out.println("线程"+Thread.currentThread().getName()+"释放许可");
                    }
                }
            }.start();
        }
    }
}

执行结果:

线程2执行
线程4执行
线程3执行
线程6执行
线程7执行

代码中将许可释放注释掉了,许可被五个线程获取了,并执行了,但许可没有释放,所以只能有5个可执行的线程。我们将许可释放的注释去掉,再看看执行结果:

线程2执行
线程4执行
线程1执行
线程1释放许可
线程6执行
线程6释放许可
线程4释放许可
线程8执行
线程8释放许可
线程2释放许可
线程5执行
线程10执行
线程10释放许可
线程5释放许可
线程9执行
线程9释放许可
线程14执行
线程14释放许可
线程13执行
线程13释放许可
线程12执行
线程12释放许可
线程16执行
线程16释放许可
线程18执行
线程18释放许可
线程17执行
线程17释放许可
线程20执行
线程20释放许可
线程3执行
线程3释放许可
线程7执行
线程7释放许可
线程11执行
线程11释放许可
线程15执行
线程15释放许可
线程19执行
线程19释放许可

从结果也可以看到,释放一个许可,剩下的线程就获取一个许可并执行。

4.闭锁与栅栏的区别

说真的,以我的理解,这两个之间区别不大:

闭锁是外部线程等待所有执行线程执行到计数器减到0,再执行外部线程的程序。闭锁的阻塞到达只执行一次。

栅栏是线程自己本身等待其它线程的计数器全部到到达状态,再执行线程自己本身的程序。栅栏的阻塞到达可以执行多次。

其实前一个的区别不算区别,不管外部线程还是线程自己本身,都可以换种写法,闭锁也可以在自己线程内部等待阻塞到达

猜你喜欢

转载自blog.csdn.net/wangzx19851228/article/details/81111741