1.LockSupport工具类
主要作用是挂起和唤醒线程,底层是由Unsafe类实现。
void park() | / |
void unpark(Thread thread | / |
void parkNanos(long nanos) | / |
park(Object blocker) | Thread 类里面有个变量 volatile Object parkBlocker 用来存放 park 方法传递的 block 对象,也就是把blocker 变量存放到了调用 park 方法的线程的成员变量里面。 |
2.AbstractQueuedSynchronizer(AQS)
全称AbstractQueuedSynchronizer,是阻塞式锁和相关同步器工具的框架,并发包中锁的底层就是使用AQS实现的。
2.1 成员变量
内部是一个FIFO的双向队列
重要的属性包括:state;Node head指向第一个被挂起的线程; Node tail指向随后一个被挂起的线程
AQS是个抽象类(需要被继承实现),中间有ConditionObject和Node两个静态内部类
![]() | ![]() |
1.state:表示资源的状态:
ReentrantLock:当前线程获得锁的可重入次数(=0表示可获得锁,>0表示锁已被占用,需要判断是否是自身锁)
ReentrantReadWriteLock:state高16位表示读状态(获取读锁的次数),低16位表示获取写锁的线程可重入次数
semaphore:state表示当前可用信号的个数
CountDownlatch:state表示计数器当前的值
state方法:1.getState:获取state状态;2. setState:设置state状态;3. compareAndSetState:CAS设置state状态
2.2 内部类Node
Node记录每个线程:
SHARED:用来标记该线程是获取共享资源时被阻挂起后放入AQS 队列的
EXCLUSIVE:用来标记线程是获取独占资源时被挂起后放入 AQS 队列的
waitStatus:记录当前线程等待状态,可以为 CANCELLED (线程被取消了),SIGNAL(线程需要被唤醒),CONDITION(线程在条件队列里面等待),PROPAGATE(释放共享资源时需要通知其他节点)
prev 记录当前节点的前驱节点,next 记录当前节点的后继节点
2.3 内部类ConditionObject
每个ConditionObject是一个单项链表队列(条件队列),其用来存放调用条件变量的await 方法后被阻塞的线程
这个条件队列的头、尾元素分别为 firstWaiter 和 lastWaiter。
2.4 介绍
本身是一个抽象类,主要通过继承的方式使用,分为独占模式和共享模式:
独占模式:只有一个线程可以访问资源;如ReentrantLock;
独占模式方法有:void acquire( int arg);void acquirelnterruptibly(int arg);boolean release(int arg);
共享模式:多个线程可以访问资源;如ReantrantReadWriteLock
共享模式方法有:void acquireShared(int arg);void acquireSharedinterruptibly(int arg);boolean releaseShared(int arg);
2.5 使用方法与原理
2.6 中实现了自定义锁,其中MySync继承了AQS,在加锁时只需在lock方法中调用mysync.acquire()和mysync.release()方法。
这些方法AQS都已经提供无需重写。需要在MySync中重写的是tryAcquire()和tryRelease()方法。
原理如下:acquire()和release()中调用了tryAcquire()和tryRelease()
子类需要重写的方法:
1.tryAcquire() / tryAcquireShared():尝试加 独占 / 共享 锁
2. tryRelease() / tryReleaseShared():尝试解锁 独占 / 共享 锁
3. isHeldExclusively:判断锁是被独占还是共享
4.newCondition();new一个ConditionObject对象
2.6 自定义锁实例
//自定义锁,不可重入锁
class MyLock implements Lock{
//独占锁
class Mysync entends AbstractQueuedSynchronizer {
@Override //尝试获取锁
protected boolean tryAcquire(int arg){
if(compareAndSetState(0, 1)){
//加锁成功
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override //尝试释放锁
protected boolean tryRelease(int arg){
setExclusiveOwnerThread(null);
setState(0);
return true;
}
@Override //是否持有独占锁,该方法重写AOS,AOS是AQS父类
protected boolean isHeldExclusively(){
return getState() == 1;
}
public Condition newCondition(){
return new ConditionObject();
}
}
private Mysync sync = new Mysync();
@Override //加锁(加锁失败后会进入等待队列等待)
public void lock(){
sync.acquire(1);
}
@Override //加锁,可打断
public void lockInterruptibly() throws InterruptedException{
sync.lockInterruptibly(1);
}
@Override //尝试加锁(加锁失败只会返回false,)
public boolean tryLock(){
sync.tryAcquire(1);
}
@Override //尝试加锁带超时(加锁失败只会返回false)
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException{
sync.tryAcquireNanos(1, unit.toNanos(time))
}
@Override //解锁
public void unlock(){
sync.release(1);
}
@Override //创建条件变量
public Condition newCondition(){
return sync.newCondition();
}
}
3.ReentrantLock原理
ReentrantLock内部的Sync锁同步器,继承了AQS,分为公平锁和非公平锁
1.非公平锁实现原理
构造器默认为非公平锁
//NonefairSync继承了AQS
public ReentrantLock(){
sync = new NonfairSync();
}
2.可重入锁原理
判断state==0时:表示锁可以被占有
state!=0时:如果当前线程==占有锁的线程:state+1;锁重入成功
如果当前线程!=占有锁的线程:枷锁失败
if(state为0){
CAS加锁成功
return true;
}
else{ //state不为0
if(当前线程=Owner中的线程){
state += 1;锁重入
return true;
}
}
4. ReentrantReadWriteLock读写锁
读读可以并发;读读或者写写互斥
注意:
读锁不支持条件变量
重入不支持升级:即持有读锁的情况下再去尝试获得写锁,会导致永久等待
重入支持降级:持有写锁的情况下尝试获取读锁
class DataContainer{
private Object data;
private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
private ReentrantReadWriteLock.ReadLock r = rw.readLock();
private ReentrantReadWriteLock.WriteLock w = rw.writeLock();
public Object read(){
r.lock();
try{
return data;
}finally{
r.unlock();
}
}
public Object write(){
w.lock();
try{
//修改data;
return data;
}finally{
w.unlock();
}
}
}
5.StampedLock
该类从JDK1.8加入,是为了进一步优化读性能。特点是在使用读锁,写锁时都必须配合戳使用。
ReentrantReadWriteLock在进行读读操作时底层还是使用AQS,CAS修改状态
public class Test{
private int data;
//创建StampedLock
private final StampedLock lock = new StampedLock();
public int read(){
//尝试乐观读
long stamp = lock.tryOptimisticRead();
//修改data
//如果校验成功(stamp戳没变表示没有其他线程干扰过)——>成功
if(lock.validate(stamp)){
return data;
}
try{
//如果失败就执行readLock锁升级,和ReentrantReadWriteLock一样
stamp = lock.readLock();
//修改data
return data;
}finally{
//解锁
lock.unlockRead(stamp);
}
}
}
缺点:不支持条件变量;不支持可重入