目录
一、ReentrantLock入门
二、AQS原理
1、AQS介绍
2、自定义锁
三、ReentrantLock实现原理
1、非公平锁的实现
加锁流程
释放锁流程
2、可重入原理
3、可打断原理
4、公平锁原理
5、条件变量原理
await流程
signal流程
相对于synchronized它具备如下特点
与synchronized一样,都支持可重入
基本语法
//获取锁
reetrantLock.lock();
try{
//临界区
}finally{
//释放锁
reentrantLock.unlock();
}
可重入
可重入指一个线程如果首次获取这把锁,他就是这把锁的拥有者,有权再次获取这把锁。如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
可打断
如果用的lock.lockInterruptibly()这个方法上锁的话,别的线程是可以通过interrupt方法打断的。比如:我的a线程正在尝试获取锁,但这个lock已经被b线程拿了,b可以如果执行Interrupt就可以把a线程正在尝试的打断直接获取失败不等待。就是一种防止无限制等待的机制,避免死等,减少死锁的产生
锁超时
有个lock.tryLock()方法,返回boolean,获取到就返回true,获取不到锁就返回false,这个方法的可以传入两个参数超时时间,第一个参数数字代表时间,第二个是单位。代表他去tryLock()尝试获取锁的时候最多等待的实践,如果是1和秒就是最多尝试等待一秒,还没拿到就返回false。也是一直超时机制,防止死锁。
公平锁
syn就是非公平的,重量级的monitor的堵塞队列就是非公平的。
ReentrantLock默认是不公平,但是我们可以通过构造方法改成公平的,传的是boolean值
条件变量
syn不满足条件的线程都在一个休息室
而ReentranLock支持多休息室,唤醒的时候也是按照休息室来唤醒
使用流程:
在new完ReentranLock之后可以用newCondition()方法创建休息室,然后就可以用new出来的condition调用await方法进入休息室等待,唤醒的话是signal()方法
Abstract Queued Synchronizer,抽象队列同步器,是阻塞式锁和相关的同步器工具框架,主要是继承他,然后重用他的功能
特点:
获取锁:tryAcquire(arg)返回boolean,aqs里面暂停和恢复用park和unpark
释放锁:tryRelease(arg)返回boolean
自定义的锁的方法基本上都是通过AQS来进行实现的
最后就是实现锁的方法,基本上都是间接调用同步器的方法来执行
class MyLock implements Lock {
//先实现同步器,实现AQS抽象类,他已经帮我们写了大多数方法,我们只需要自定义4个方法
class MySync extends AbstractQueuedSynchronizer{
@Override
protected boolean tryAcquire(int arg) {
//尝试加锁
if(compareAndSetState(0,1)){
//这个用的是CAS的无锁机制加锁防止多线程安全问题
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
//尝试释放锁
setExclusiveOwnerThread(null);
//这个state是volatile修饰,防止指令重排
setState(0);
return true;
}
@Override
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.acquireInterruptibly(1);
}
@Override//尝试加锁,不成功就放弃
public boolean tryLock() {
return sync.tryAcquire(1);
}
//尝试加锁,超时就进入队列
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
long waitTime = unit.toNanos(time);
return sync.tryAcquireNanos(1,waitTime);
}
@Override
public void unlock() {
//他这个释放锁,调用的是release方法,这个方法还会唤醒堵塞队列中的一个线程
sync.release(1);
}
@Override
public Condition newCondition() {
// sync.newCondition();
return sync.newCondition();
}
}
他底层也是有个抽象Sync类继承了AQS,他有两个实现NonfairSync非公平锁和FairSync公平锁
他先用cas的compareAndSetState尝试加锁,当没有竞争时,如果上锁成功就setExclusiveOwnerThread改成当前线程
第一个竞争出现时,他会自己tryAcquire重试一次,如果成功就加锁成功。
如果还是加锁失败就会构建node队列,head指向的是第一个节点称为dummy哑元或哨兵,是占位的,并不关联线程
然后回进入一个死循环汇总不断尝试获取锁,失败后进入park阻塞
如果自己是紧邻head的第二位,那么可以再次tryAcquire尝试获取锁,当仍然state为1,失败
将前驱的node,即head的waitStatus改为-1(表示要堵塞了,head是status-1就可以后面有责任唤醒它),这次返回false
这个时候还会再循环一遍,进入方法因为前驱节点为-1了就回返回true,然后就会调用park方法堵塞当前线程
当有多个节点都park堵塞的时候就会一个个进入,然后每个state上面的小三角都是-1表示前驱节点有责唤醒它
会tryRelease,如果成功直接设置exclusiveOwnerThread为null,然后state为0
当前队列不为null时,并且head的waitStatus为-1,他就会找到队列中里head最近的node,unpark恢复他的运行,然后就会执行上面加锁的方法。
但是这个是非公平锁,假如这个时候突然来了个不在队列里面的线程4,就会跟刚刚唤醒的线程竞争,如果4先拿到锁设置为exclusiveOwnerThread,那么1将会重新park进入阻塞
以非公平锁为例,获取锁会调用nonfairTryAcquire
他会先判断state状态,如果为0说明没有加锁,直接加锁
如果不为0说明上锁了就会判断当前线程是不是和exclusiveOwnerThread里面的一样,如果一样的话就说明是重入了,直接s
释放的时候就会调用tryRelease方法,调用完就会state--,如果当减到0的时候,就会让锁释放掉,设置exclusiveOwnerThread为null,让state为0
不可打断模式
默认情况是不可打断的,线程在没变法立刻获得锁的时候,就会不断循环尝试获取锁,仍然不成功会进入park,进入park是可以被其他线程被唤醒。有线程来打断他,打断标记默认值是false,一旦被打断就会色设置为true,但是他就是改个这个标记,然后又重新进入循环park了。就是唤醒了但是没有拿到锁还是继续阻塞,只是有一个true标记
不可打断模式下,即使被打断,仍会驻留在AQS队列里面,等获得锁后才能继续执行,在获得锁以后才知道其他线程打断我了(打断标记被设置为true)
可打断模式
当调用doAcquireInterruptibly,也是去获取锁,然后park进入队列,这个时候别的线程唤醒它继续执行,直接是抛出InterruptedException异常就直接不用循环等待了,实现了可打断
非公平锁是nonfairTryAcquire去获取锁,获取锁的时候如果为state为0说明没有人拿锁,他会直接去抢锁,没有任何判断
公平锁会多个判断条件,如果堵塞队列还有线程就不会去拿锁
总结:进入condition队列,清空锁并且唤醒线程,最后就是使用park进行阻塞。
总结:signal完成了条件队列清除(单项链表清除),然后就是把对应的节点全部送去Sync队列。如果失败可能就是队列满了或者是超时了。最后就是取出前驱节点修改状态。