CountDownLatch
官方解释:一种同步辅助,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成countDownLatch用给定的计数进行初始化。由于对countDown方法的调用,await方法会阻塞直到当前计数达到0,之后所有等待的线程都会被释放,所有后续的await调用都会立即返回。这是一个一次性现象——计数不能重置。如果需要重置计数的版本,可以考虑使用CyclicBarrier。countDownLatch是一个通用的同步工具,可以用于多种目的。用一个计数初始化的CountDownLatch作为一个简单的开/关闩锁,或gate:所有调用await的线程在gate处等待,直到它被调用countDown的线程打开。可以使用初始化为N的CountDownLatch使一个线程等待,直到Nthreads完成某些操作,或者某些操作已经完成N次。CountDownLatch的一个有用属性是,它不要求调用countDown的线程在继续之前等待计数达到0,它只是阻止任何线程继续等待,直到所有线程都可以通过。
案例一、
package duoxiancheng2;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* @author yeqv
* @program A2
* @Classname T2
* @Date 2022/2/8 8:38
* @Email w16638771062@163.com
*/
public class T2 {
//CountDownLatch
CountDownLatch latch = new CountDownLatch(1);
public static void main(String[] args) {
T2 t = new T2();
new Thread(() -> t.a(), "A").start();
new Thread(() -> t.b(), "B").start();
}
void a() {
System.out.println(Thread.currentThread().getName() + "已启动");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown();
System.out.println(Thread.currentThread().getName() + "已结束");
}
void b() {
System.out.println(Thread.currentThread().getName() + "已启动");
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "已结束");
}
}
结果:线程A,B启动。线程A在执行,线程B在等待
当线程A完成操作开锁之后,线程B开始执行(这里是某组操作完成时,也就是执行了countDown()方法。不是说线程A执行完。可能执行到中间就已经开锁了)
ReentrantLock
ReentrantLock 最大的特点公平锁
即哪个线程等待的时间最长优先执行
ReentrantLock常常对比着synchronized来分析,我们先对比着来看然后再一点一点分析。
(1)synchronized是独占锁,加锁和解锁的过程自动进行,易于操作,但不够灵活。ReentrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活。
(2)synchronized可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;ReentrantLock也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。
(3)synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以相应中断。
public class T3 {
Lock lock = new ReentrantLock(true);//设置为true公平锁,效率低但公平
void m() {
for (int i = 0; i <= 20; i++) {
lock.lock();
System.out.println(Thread.currentThread().getName());
try{
TimeUnit.SECONDS.sleep(1);
}catch(Exception e){
e.printStackTrace();
}
lock.unlock();
}
}
public static void main(String[] args) {
var t = new T3();
new Thread(t::m,"T1").start();
new Thread(t::m,"T2").start();
new Thread(t::m,"T3").start();
new Thread(t::m,"T4").start();
}
}
ReadWriteLock
ReadWriteLock也是一个接口,提供了readLock和writeLock两种锁的操作机制,一个资源可以被多个线程同时读,或者被一个线程写,但是不能同时存在读和写线程。
假设在程序中定义一个共享的数据结构用作缓存,它大部分时间提供读服务(例如:查询和搜索),而写操作占有的时间很少,但是写操作完成之后的更新需要对后续的读服务可见。
特性:
读-读不互斥
读-写互斥
写-写互斥
案例、
public class TestReadWriteLock{
public static void main(String[] args){
ReadWriteLockDemo rw = new ReadWriteLockDemo();
// 一个线程进行写
new Thread(new Runnable(){
public void run(){
rw.set((int)(Math.random()*100));
}
},"Write:").start();
// 100个线程进行读操作
for(int i=0; i<100; i++){
new Thread(new Runnable(){
public void run(){
rw.get();
}
},"Read:").start();
}
}
}
class ReadWriteLockDemo{
private int number = 0;
private ReadWriteLock lock = new ReentrantReadWriteLock();
// 读
public void get(){
lock.readLock().lock(); // 上锁
try{
System.out.println(Thread.currentThread().getName()+":"+number);
}finally{
lock.readLock().unlock(); // 释放锁
}
}
// 写
public void set(int number){
lock.writeLock().lock();
try{
System.out.println(Thread.currentThread().getName());
this.number = number;
}finally{
lock.writeLock().unlock();
}
}
}