作者
纲要
AQS
全称 AbstractQueueSynchronizer ,是阻塞式锁和相关的同步器工具的框架;
Lock 的底层就是 AQS
特点:
- 用 state 属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁
- getState 获取 state 状态
- setState 设置 state 状态
- compareAndSetState 通过 CAS 机制设置 state 状态
- 提供了基于 FIFO 的等待队列,类似于 Monitor 的 EntryList
- 条件变量来实现等待、唤醒机制,支持多个条件变量,类似 Monitor 的 WaitSet
子类主要实现的方法
- tryAcquire
- tryRelease
- tryAcquireShared
- tryReleaseShared
- isHeldExclusively
获取锁的姿势
if (!tryAcquire(arg)){
// 入队,可以选择阻塞当前线程 park unpark
}
释放锁的姿势
if (tryRelease){
// 让阻塞线程恢复运行
}
读写锁
ReentrantReadWriteLock
当读操作远远高于写操作时,这时候使用读写锁让读-读可以并发,提高性能。
类似于数据库中 select … from … lock in share mode
提供了一个数据容器类内部分别使用读锁保护数据的 read() 方法,写锁保护数据的 write() 方法; ```java package top.simba1949;
import java.util.concurrent.locks.*;
/**
- @author Anthony
@date 2020/11/1 9:00 */ public class App1 { public static void main(String[] args) throws InterruptedException {
DataContainer dataContainer = new DataContainer();
new Thread(() -> dataContainer.read(), "t1").start();
Thread.sleep(10);
new Thread(() -> dataContainer.read(), "t2").start();
new Thread(() -> dataContainer.write(), "t1").start();
Thread.sleep(10);
new Thread(() -> dataContainer.write(), "t2").start();
} }
class DataContainer{ private Object data;
private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
// 读锁
private ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
// 写锁
private ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
public Object read(){
// 加读锁
readLock.lock();
try {
Thread.sleep(5000);
System.out.println(" read:" + Thread.currentThread().getName() + ">" + System.currentTimeMillis());
return data;
} catch (InterruptedException e) {
e.printStackTrace();
return null;
} finally {
// 释放读锁
readLock.unlock();
}
}
public void write(){
// 加写锁
writeLock.lock();
try {
Thread.sleep(5000);
System.out.println("write:" + Thread.currentThread().getName() + ">" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放写锁
writeLock.unlock();
}
}
}
<a name="n55lY"></a>
## StampedLock
该类自 JDK8 加入,是为了进一步优化读性能,它的特点是在使用读锁、写锁时都必须配合【戳】使用<br />缺点:
1. StampedLock 不支持条件变量
1. StampedLock 不支持可重入
加解读锁
```java
StampedLock stampedLock = new StampedLock();
stampedLock.unlockRead(stamp);
加解写锁
StampedLock stampedLock = new StampedLock();
stampedLock.unlockWrite(stamp);
乐观读,StampedLock 支持 tryOptimisticRead()
方法(乐观读),读取完毕后需要再做一次戳校验,如果校验成功,表示这期间确实没有写操作,数据可以安全使用,如果校验没有通过,需要重新获取读锁,保证数据安全
StampedLock stampedLock = new StampedLock();
long stamp = stampedLock.tryOptimisticRead();
if (!stampedLock.validate(stamp)){
// 锁升级
}
demo
提供一个数据容器内部分别使用读锁保护数据的read方法,写锁保护数据write方法
public class DataContainerStamped {
private int data;
private final StampedLock stampedLock = new StampedLock();
public DataContainerStamped(int data) {
this.data = data;
}
public int read(int readTime){
long stamp = stampedLock.tryOptimisticRead();
// 如果校验通过直接返回数据
if (stampedLock.validate(stamp)){
return data;
}
// 校验不通过,说明数据已经被更新,乐观读锁升级为读锁
try {
stamp = stampedLock.readLock();
return data;
} finally {
stampedLock.unlockRead(stamp);
}
}
public void write(int newData){
long stamp = stampedLock.writeLock();
try {
this.data = newData;
} finally {
stampedLock.unlockWrite(stamp);
}
}
}
Semaphore
信号量,用来限制能同时访问共享资源的线程上限,其本质上是一个“共享锁”;
实现原理:内部维护了一个共享锁,Semaphore 初始化的时候相当于初始化共享锁的个数,当 acquire 的时候共享锁的个数减一,当为零的时候不能获取到共享锁。当 release 的时候共享锁的个数增加一。同时也支持公平锁,在构造函数初始化的时候可以指定。
package top.simba1949;
import java.util.concurrent.Semaphore;
/**
* @author Anthony
* @date 2020/11/1 9:00
*/
public class App1 {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
// 获取次信号量
semaphore.acquire();
System.out.println(Thread.currentThread().getName());
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放信号量
semaphore.release();
}
}, "t" + i).start();
}
}
}
CountDownLatch
用于进行线程同步写作,等待所有线程完成倒计时。
其中构造方法用来初始化等待计数值,await() 用来等待计数归零,countDown() 用来让计数减一;
实现原理:内部通过共享锁实现,CountDownLatch 在创建的时候需要指定初始化参数,该参数就是共享锁的总次数。当某个线程调用 awit 的时候,会判断是否还持有共享锁,一直会等到共享锁的状态为0为止;当其他线程调用 countDown() 方法时,则执行释放共享锁状态,共享锁的次数减1;注意CountDownLatch不能回滚重置;
package top.simba1949;
import java.util.concurrent.CountDownLatch;
/**
* @author Anthony
* @date 2020/11/1 9:00
*/
public class App1 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
System.out.println(System.currentTimeMillis());
countDownLatch.countDown();
}, "t" + i).start();
}
countDownLatch.await();
System.out.println("ending");
}
}
CyclicBarrier
CyclicBarrier 的作用就是会让所有线程都等待完成后才会继续下一步行动。
可以用于多线程计算数据,最后合并计算结果的场景。
实现原理:内部是使用可重入锁 ReentrantLock 和 Condition,ReentrantLock 控制每个线程的锁的状态,Condition 对线程进行等待、唤醒机制,当线程发生中断户这话完成等待时,对线程进行唤醒。
// parties 是参与线程的个数
// 第二个构造方法有一个 Runnable 参数,这个参数的意思是最后一个到达线程要做的任务
public CyclicBarrier(int parties);
public CyclicBarrier(int parties, Runnable barrierAction);
// 等待所有的线程都到达指定的临界点
cyclicBarrier.await();
// 获取当前有多少个线程阻塞等待在临界点上
cyclicBarrier.getNumberWaiting();
// 用于查询阻塞等待的线程是否被中断
cyclicBarrier.isBroken();
// 将屏障重置为初始状态,如果当有线程正在临界点等待的话,将抛出异常
cyclicBarrier.reset();
CyclicBarrierc 和 CountDownLatch区别
- CountDownLatch 是一次性的,CyclicBarrier 是可循环利用的
CountDownLatch 参与的线程的职责是不一样的,有的在倒计时,有的在等待倒计时结束。CyclicBarrier 参与的线程职责是一样的。
线程安全的集合类
线程安全的集合类可以分为三类
遗留的线程安全集合,如:Hashtable,Vector
- 使用Collections装饰的线程安全集合,使用装饰模式,本质上还是使用 synchronized 修饰,例如:
- Collections.synchronizedCollection()
- Collections.synchronizedList()
- Collections.synchronizedMap()
- Collections.synchronizedSet()
- Collections.synchronizedNavigableMap()
- Collections.synchronizedNavigableSet()
- Collections.synchronizedSortedMap()
- Collections.synchronizedSortedSet()
- j.u.c 包下的集合类
- Blocking 大部分实现基于锁,并提供用来阻塞的方法;
- CopyOnWrite 之类的容器修改开销相对较重,适用于读多写少的操作;
- Concurrent 类型的容器;