开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情
本文主要讲解Java并发中的CountDownLatch、CyclicBarrier、Semaphore和读写锁ReadWriteLock。
8. 常用的辅助类
CountDownLatch:减法计数器。里面的参数表示总数量。
await方法等待计数器归0,再往下执行;cutDown使计数减1。
每次有线程调用countDown数量减1,假设计数器变为0,counrDownLatch.await()就会被唤醒,继续执行!
下面看一个例子。
- 【示例作用】可以理解教室内6个学生要离开教室,用CountDownLatch设置一个学生总数量,也就是6个。离开一个学生,计数就减1,等教室的6个学生都离开了,再将门关上。
- 用法:1.
CountDownLatch countDownLatch = new CountDownLatch(6);2.业务逻辑和计数减一,countDownLatch.countDown();3.等待计数归0继续向下执行`countDownLatch.await();相当于是阻塞线程。
public static void main(String[] args) throws InterruptedException {
//总数是6,必须要执行任务的时候,再使用!
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "Go out");
countDownLatch.countDown(); //计数-1
},String.valueOf(i)).start();
}
countDownLatch.await();//等待计数器归0,然后再向下执行
//倒计时操作
System.out.println("Close Door");
}
CyclicBarrier加法计数器。两个功能:
1.单纯的加一计数2.计数加一,加到设定值后触发(runnable)Lambda表达式的内容。
await();等待计数器变成设定值,然后执行上面计数器里面那个runnable
【示例理解】:通过new CyclicBarrier初始化一个要计数到7之后再执行Runnable(集齐七颗龙珠再召唤神龙)。
public static void main(String[] args) {
/**
* 集齐7颗龙族召唤神龙
*/
//召唤龙族的线程
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("召唤神龙成功!");
});//可以传一个参数(计数),也可以传一个计数一个线程两个参数。(计数完执行线程)
for (int i = 1; i <= 7; i++) {
//下面这个lambda怎么拿到上面循环的这个i
final int temp = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "收集" + temp +"个龙珠");
try {
cyclicBarrier.await();//等待计数器变成7,然后执行上面计数器里面那个runnable
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
Semaphore信号量:解决的类似于限流的问题。比如有n个槽位,槽位满了就会等待槽位被释放为止。
- acquire获取,假设槽位已经满了,就会等待被释放为止。
- release释放,会讲当前的信号量释放+1,然后唤醒等待的线程!
- 作用:多个共享资源互斥的使用!并发限流,控制最大的线程数。
【停车位例子】:这个例子主要实现的功能:6辆车需要停车,但只有三个车位,三个车先停进去(acquire),然后停两秒后再出来(release)。
/**
* Semaphore原理:
* acquire获取,假设槽位已经满了,就会等待被释放为止。
* release释放,会讲当前的信号量释放+1,然后唤醒等待的线程!
* 作用:多个共享资源互斥的使用!并发限流,控制最大的线程数。
*/
//Semaphor信号量
public class SemaphoreDemo {
public static void main(String[] args) {
//线程数量:停车位!限流!
Semaphore semaphore = new Semaphore(3);
//6辆车
for (int i = 1; i <= 6; i++) {
new Thread(()->{
//acquire得到
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "抢到车位");
TimeUnit.SECONDS.sleep(2);//停两秒钟
System.out.println(Thread.currentThread().getName() + "离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
//release释放
}).start();
}
}
}
9. 读写锁
独占锁(写锁):一次只能被一个线程占有。
共享锁(读锁):多个线程同时占有。
对于普通无锁的读写缓存,会存在1写入a,中间插入2写入b,甚至会插入读取1,然后再是1写入完成,这种非常不安全。这里就可以加锁。普通lock锁ReentrantLock与读写锁ReadWriteLock控制的粒度不同,普通的Lock锁使用方式前面也讲过了。下面主要举读写锁的例子。
- 与普通lock使用方法一样。第一步:new ReentrantReadWriteLock(); 第二步加锁:readWriteLock.writeLock().lock();第三步在finally中释放锁:readWriteLock.writeLock().unlock();
- 业务代码是在try语句中。
- 读写锁有两种情况,读锁加锁和写锁加锁,分别是(写锁)readWriteLock.writeLock()和(读锁)readWriteLock.readLock().lock()。
private volatile Map<String,Object> map = new HashMap<>();
//读写锁,更加细粒度的控制
private ReadWriteLock readWriteLock= new ReentrantReadWriteLock();
//存(想要在写的时候只有一个线程写)
public void put(String key,Object value){
readWriteLock.writeLock().lock();//写锁加锁
try {
System.out.println(Thread.currentThread().getName() + "写入"+ key);
map.put(key,value);
System.out.println(Thread.currentThread().getName() + "写入OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();//写锁解锁
}
}
public void get(String key){
readWriteLock.readLock().lock();//读锁加锁
try {
System.out.println(Thread.currentThread().getName() + "读取"+ key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取OK");
} catch (Exception e){
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();//读锁解锁
}
}