目录
1.java为什么要实现自己的管程机制?
2.AQS
?
2.1 state
?2.2 同步等待队列和条件等待队列
?2.3 自定义AQS
3.reentrantLock详解
3.1 源码分析(TODO)
1.java为什么要实现自己的管程机制?
管程:操作系统底层实现的对线程并发问题实现的监视器机制,管程内部大致分为:同步等待队列和等待唤醒队列。
同步等待队列的实现:synchronized在jvm底层保证线程能够串行化的操作同步代码块内的程序。
等待唤醒队列的实现:java在object类中实现了monitor机制,每个对象都有一个monitor,可以手动调用wait/notify/notifyall方法操作线程。
synchronized缺点:不能手动加锁解锁,synchronized内部加解锁是自动的。
为了解决以上场景java实现了自己的管程机制AQS机制:
同步等待队列(双向链表结构):cas实现(内部通过volatile int state状态控制入队出队) 加解锁有自己的实现
条件等待队列(单向链表结构):Condition接口实现(await/signal/signalAll等待;唤醒机制)
2.AQS
基于上述描述讲解AQS。
AQS的特性:
? ? ? ? 1. 阻塞等待队列
? ? ? ? 2. 共享/独占
? ? ? ? 3. 公平/非公平
? ? ? ? 4. 可重入
? ? ? ? 5. 允许中断
AQS的实现类如下图。
AQS的资源共享方式
????????独占:只有一个线程能执行,如reentrantLock
????????共享:多个线程同时执行,如Semaphore和CountDownLatch
AQS的两种队列:同步等待队列和条件等待队列
????????同步等待队列:主要用于维护获取锁失败的线程
????????条件等待队列:调用await时会释放锁进入条件等待队列,调用signal时唤醒线程放入同步等待队列
AQS中实现的方法:
????????共享:tryAcquireShared获取共享锁,releaseShared解锁,tryReleaseShared尝试解锁
????????独占:tryAcquire获取独占锁,release解锁,tryRelease尝试解锁
2.1 state
AQS中volatile int修饰的state:表示AQS的状态
state的访问方式:
getState(),setState(),compareAndSetState()
?
?2.2 同步等待队列和条件等待队列
同步等待队列是一个双向链表的队列。
条件等待队列是一个单向链表结构,条件调用await唤醒以后进入同步等待队列,同步等待队列阻塞进入条件等待队列。
?2.3 自定义AQS
自定义自己的AQS实现类:
1.继承AbstractQueuedSynchronizer
2.实现自己的抽象方法tryAcquire和tryRelease方法。其他方法都是AQS已经封装好的直接调用即可。
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class TestLock extends AbstractQueuedSynchronizer {
private int state_0 = 0;//0-解锁
private int state_1 = 1;//1-加锁
//加锁逻辑:使用CAS实现,如果修改成功返回true并且设置独占锁,否则返回false
@Override
protected boolean tryAcquire(int arg) {
if(compareAndSetState(state_0,arg)){
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
//释放锁:将锁状态还原
@Override
protected boolean tryRelease(int arg) {
setExclusiveOwnerThread(null);
setState(arg);
return true;
}
public void lock(){
acquire(state_1);
}
public boolean tryLock(){
return tryAcquire(state_1);
}
public void unLock(){
release(state_0);
}
public boolean tryUnLock(){
return tryRelease(state_0);
}
}
class TestAQS{
private static int sum = 0;
private static TestLock testLock = new TestLock();
public static void main(String[] args) throws InterruptedException {
for (int i =0 ;i<3;i++){
Thread thread = new Thread(()->{
testLock.lock();
try{
for (int j=0;j<10000;j++){
sum++;
}
}finally {
testLock.unLock();
}
});
thread.start();
}
//休眠1秒等待线程执行完毕
Thread.sleep(1000);
System.out.println("sum:"+sum);
}
}
3.reentrantLock详解
reentrantLock是一个基于AQS框架的锁,可以手动加解锁,比synchronized性能要好一点,支持解决并发安全问题,支持公平和非公平锁。
synchronized与reenTrantLock的区别:最重要的区别就是reenTrantLock需要在finally块中手动解锁。
reenTrantLock的特点:
? ? ? ? 1.可中断
? ? ? ? 2.可设置超时时间
? ? ? ? 3. 可以设置公平锁
? ? ? ? 4. 支持多个条件变量
? ? ? ? 5. 支持可重入
3.1 源码分析(TODO)
关注点:
1.加解锁
2.公平,非公平,可重入实现
3.线程竞争锁失败入队阻塞逻辑和获取锁的线程释放锁唤醒阻塞线程竞争锁的逻辑实现
重点debug下面代码:lock.lock和lock.unlock源码部分
1.lock.lock(加锁,默认非公平锁实现):CAS判断,如果修改成功,设置独占锁,没有走公平锁逻辑获取锁。
? ? ? ? 1.1 公平锁逻辑分为几步:
? ? ? ? ? ? ? ? 1.1.1 尝试获取锁(tryAcquire(int acquires)),分为两步:1.线程状态state为0执行CAS独占锁逻辑。2.如果当前线程是独占锁执行重入锁逻辑,将state加1。
? ? ? ? ? ? ? ? 1.1.2 入队(addWaiter(Node.EXCLUSIVE)),桟入队操作有点儿复杂(TODO),简单来说就是双向链表的建立过程。
? ? ? ? ? ? ? ? 1.1.3 获取队列(boolean acquireQueued(final Node node, int arg)),也是分为两步:1.如果是只有一个节点并且获取锁成功返回中断标志位为false;2.如果不是只有一个节点通过waitstatus标志位(-1 唤醒锁,大于0)回去尝试唤醒锁或者(TODO,看不懂了,感觉有两个队列)
? ? ? ? 1.2 设置中断标志位(selfInterrupt()):前面的操作会取消中断标志位,这一步是为了复位中断标志位
2.lock.unlock(解锁):解锁逻辑很简单,分为两步
???2.1 尝试将重入标志state值减1判断是否等于0,tryRelease(arg),如果为0,复原独占锁为null
? ? ? ? 2.2? 上一步的state为0时并且链表不为空,唤醒标志位waitstatus不为0,阻塞线程
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
private static int sum = 0;
private static Lock lock = new ReentrantLock();
//private static TulingLock lock = new TulingLock();
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++) {
Thread thread = new Thread(()->{
//加锁
lock.lock();
try {
// 临界区代码
// TODO 业务逻辑:读写操作不能保证线程安全
for (int j = 0; j < 10000; j++) {
sum++;
}
} finally {
// 解锁
lock.unlock();
}
});
thread.start();
}
Thread.sleep(2000);
System.out.println(sum);
}
}
|