JAVA并发编程基础面试题及详解2

        目录

1.CountDownLatch和Semaphore的区别和底层原理

2.CountDownLatch和CyclicBarrier的区别和底层原理

3.总结


1.CountDownLatch和Semaphore的区别和底层原理

        CountDownLatch和Semaphore是JavaApi级别的共享锁,他们底层都是通过改变指定的state变量来控制并发个数的。

        CountDownLatch和Semaphore(信号量)的state变量使用逻辑有点相反的意思:Semaphore(信号量)是state为0则线程不能获取锁执行锁后面的代码。获取一次state变量减一(也可以是指定的值),释放一次变量加1(也可以是指定的值),不为0则其他线程可以继续获取锁。CountDownLatch是在state变量为0后才能唤醒所有执行了await()方法的线程,否则继续阻塞这些线程。我们看几个demo,

CountDownLatch的demo:

package ReentrantLockTest;

import java.util.concurrent.CountDownLatch;

public class CountDownLatchTest {
    private static Thread t1,t2,t3,t4,t5;
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(5);
        t1 = new Thread(()->{
            System.out.println("线程1开始运行");
            countDownLatch.countDown();
            System.out.println("线程1执行countDwon()方法," +
                            "当前count:" + countDownLatch.getCount());
        });
        t2 = new Thread(()->{
            System.out.println("线程2开始运行");
            countDownLatch.countDown();
            System.out.println("线程2执行countDwon()方法," +
                            "当前count:" + countDownLatch.getCount());
        });
        t3 = new Thread(()->{
            System.out.println("线程3开始运行");
            countDownLatch.countDown();
            System.out.println("线程3执行countDwon()方法," +
                            "当前count:" + countDownLatch.getCount());
        });
        t4 = new Thread(()->{
            System.out.println("线程4开始运行");
            countDownLatch.countDown();
            System.out.println("线程4执行countDwon()方法," +
                            "当前count:" + countDownLatch.getCount());
        });
        t5 = new Thread(()->{
            System.out.println("线程5开始运行");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            countDownLatch.countDown();
            System.out.println("线程5执行countDwon()方法," +
                    "当前count:" + countDownLatch.getCount());
        });
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
        Thread.sleep(100);
        System.out.println("主线程开始阻塞");
        countDownLatch.await();
        System.out.println("主线程开始执行");
    }
}

运行结果:

 从这个结果可以看到,只有当线程5执行了countdown()方法让count减为0的时候,主线程才能被唤醒。

我们在看 一个CountDownLatch的demo

package ReentrantLockTest;

import java.util.concurrent.CountDownLatch;

public class ShareLockTest {
    private static Thread t1,t2,t3,t4,t5;
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        t1 = new Thread(()->{
            System.out.println("线程1被阻塞");
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程1开始执行");
        });
        t2 = new Thread(()->{
            System.out.println("线程2被阻塞");
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程2开始执行");
        });
        t3 = new Thread(()->{
            System.out.println("线程3被阻塞");
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程3开始执行");
        });
        t4 = new Thread(()->{
            System.out.println("线程4被阻塞");
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程4开始执行");
        });
        t5 = new Thread(()->{
            System.out.println("线程5被阻塞");
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程5开始执行");
        });
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
        Thread.sleep(2000);
        System.out.println("主线程将执行countDown()方法,执行后count:" + (countDownLatch.getCount() -1));
        countDownLatch.countDown();
    }
}

 这个结果可以看出只有当主线程执行了countDown方法,让state减1变为0后,所有被await()方法阻塞的线程才会开始执行。

我们来看看Semaphore的demo:

package ReentrantLockTest;

import java.util.concurrent.Semaphore;

public class SemaphoreTest {
    private static Thread t1,t2,t3;
    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore = new Semaphore(7);
        t1 = new Thread(()->{
            System.out.println("线程1尝试获取锁。。。。。。");
            boolean b = semaphore.tryAcquire(2);
            if (b) {
                System.out.println("线程1获取到锁,开始执行程序");
            } else {
                System.out.println("线程1未获取到锁,不执行程序");
            }
        });
        t2 = new Thread(()->{
            System.out.println("线程2尝试获取锁。。。。。。");
            boolean b = semaphore.tryAcquire(3);
            if (b) {
                System.out.println("线程2获取到锁,开始执行程序");
            } else {
                System.out.println("线程2未获取到锁,不执行程序");
            }
        });
        t3 = new Thread(()->{
            System.out.println("线程3尝试获取锁。。。。。。");
            boolean b = semaphore.tryAcquire(3);
            if (b) {
                System.out.println("线程3获取到锁,开始执行程序");
            } else {
                System.out.println("线程3未获取到锁,不执行程序");
            }
        });
        t1.start();
        Thread.sleep(100);
        t2.start();
        Thread.sleep(100);
        t3.start();
    }
}

执行结果:

 从这个结果可以看出,线程3获取锁的时候semaphre的信号量只剩2个了,不够三个,所以没有获取到锁,可以修改代码让线程3循环获取锁,等线程1或者线程2释放锁后,线程3就可以获取到锁了。

2.CountDownLatch和CyclicBarrier的区别和底层原理

        CyclicBarrier也有一个变量parties ,和CountDownLatch的原理很像,但是有一点区别的。

        CountDownLatch底层是通过继承AQS框架实现的,CyclicBarrier底层是通过ReentrantLock和Condition实现的;CountDownLatch的count变量只能使用一次,CyclicBarrier 的parties 可以重置后重复使用。

        CyclicBarrier的原理:CyclicBarrier是线程获取到锁后count减一,如果当前count没有为0则阻塞,到阈值后所有阻塞线程同时执行。假如定义了 一个CyclicBarrier的阈值为5,一个线程获取到锁则先执行await阻塞,count减1,如果count到0时,唤醒所有等待的线程,重置count为parties ,后面的线程如果没有将count减到0则继续阻塞,原理一样。

        通俗易懂的来讲:我把CyclicBarrier理解为一个大巴车,阈值就是大巴车上满座的座位数parties,线程是乘客,count是已经空座数量,当乘客上车后,count减一,只有当空座为0大巴车才会发车,结束后座位清空,parties和count相等,大巴车已经被占用的座位数(parties )又恢复成原来的座位数。

我们看一个CyclicBarrier的demo

package ReentrantLockTest;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class ShareLockTest {
    private static Thread t1,t2,t3;
    public static void main(String[] args) throws InterruptedException {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
        t1 = new Thread(()->{
            try {
                System.out.println("线程1尝试获取锁");
                cyclicBarrier.await();
                System.out.println("线程1开始执行代码");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        });
        t2 = new Thread(()->{
            try {
                System.out.println("线程2尝试获取锁");
                cyclicBarrier.await();
                System.out.println("线程2开始执行代码");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        });
        t3 = new Thread(()->{
            try {
                System.out.println("线程3尝试获取锁");
                cyclicBarrier.await();
                System.out.println("线程3开始执行代码");
                cyclicBarrier.reset();
                System.out.println("重置Parties后Parties的个数:" + cyclicBarrier.getParties());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        });
        t1.start();
        Thread.sleep(100);
        t2.start();
        Thread.sleep(100);
        t3.start();
    }
}

执行结果:

3.总结

        本篇文章主要是介绍了一下三个共享锁的原理,区别和基本用法,底层源码希望大家能自己好好看一看,都不是很难,还是很有意思的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值