目录
1.CountDownLatch和Semaphore的区别和底层原理
2.CountDownLatch和CyclicBarrier的区别和底层原理
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.总结
本篇文章主要是介绍了一下三个共享锁的原理,区别和基本用法,底层源码希望大家能自己好好看一看,都不是很难,还是很有意思的。