管程是管理共享变量和对共享变量操作的过程;上文讲解了synchronized在jvm层面实现了MESA管程模型,本文阐述AQS在java层面实现MESA管程模型
AQS--AbstractQueuedSynchronizerAQS是java层面实现管程的同步队列抽象类,抽象类实现管程的同步等待队列和条件等待队列;而加锁和解锁的逻辑由具体的子类实现,只是对外提供模板方法,由程序员自己实现加解锁的逻辑。
AQS特性
阻塞等待队列;共享/独占;公平/非公平;可重入;可中断
AQS内部核心
state变量:是volatile修饰的int类型,能否获取锁就是通过CAS操作设置state变量来控制
同步等待队列:CAS尝试修改state相当于竞争锁,获得锁成功则执行业务逻辑,竞争锁失败则进入同步等待队列并调用LockSupport.park()方法阻塞当前线程,等待其他获得锁的线程调用LockSupport.unpark()方法唤醒该线程并出队;同步等待队列是双向链表结构
条件等待队列:Condition,await()/signal()、signalAll(),阻塞唤醒机制来对线程进行入队出队操作,单向链表结构
Condition接口:await()/signal()、signalAll()
两种共享资源:
SHARED-共享,多个线程可以同时执行,如Semaphore/CountDownLatch
EXCLUSIVE-独占,只有一个线程能执行,如ReentrantLock
中断:线程t1调用lockInterruptibly()方法竞争锁,此时锁已经被t2占有,t1竞争锁失败被阻塞,t2执行完了并调用t1.interrupt()中断线程t1,t1获取锁失败会抛出中断异常,最终执行catch逻辑。
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("t1启动...");
try {
lock.lockInterruptibly();
try {
log.debug("t1获得了锁");
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("t1等锁的过程中被中断");
}
}, "t1");
lock.lock();
try {
log.debug("main线程获得了锁");
t1.start();
Thread.sleep(1000);
t1.interrupt();
log.debug("线程t1执行中断");
} finally {
lock.unlock();
}
}
ReentrantLock
ReentrantLock是基于AQS框架实现的一种线程并发访问的同步手段,它的功能类似与synchronized是一种互斥锁,可以保证线程安全。
ReentrantLock与synchronized优缺点和异同点
1、ReentrantLock实现了公平锁和非公平锁,而synchronized是非公平锁(先进后出的栈结构通过单向链表实现)
2、ReentrantLock可以显示的控制加锁和解锁的逻辑,synchronized基于jvm实现隐示自动加解锁
3、synchronized是不可以被中断的,ReentrantLock是可以被中断的(lockInterruptibly())
4、ReentrantLock获得锁的方式灵活多变如:lock(),tryLock(),tryLock(time, unit),lockInterruptibly(),synchronized能修饰方法和代码块。
5、它们都是可重入锁
6、synchronized是基于jvm实现,ReentrantLock是jdk实现的
可重入:thread线程变量记录判断是否同一线程进入,表示可重入
可中断
锁超时:tryLock()--立即超时,tryLock(time, unit)--超时时间
条件变量
ReentrantLock源码执行逻辑跟踪
for (int i = 0; i < 3; i++) {
Thread thread = new Thread(()->{
lock.lock();
try {
for (int j = 0; j < 10000; j++) {
sum++;
}
} finally {
lock.unlock();
}
});
thread.start();
}
Thread.sleep(2000);
System.out.println(sum);
竞争锁的逻辑流程
当t1和t2都入队以后,t0释放锁,将state设置为0,并将AQS的exclusiveOwnerThread属性设置为null,设置waitStatus为0,并调用LockSupport.unpark(t1)唤醒t1线程
t1唤醒后将thread属性设置为null,然后调用tryLock()方法竞争锁,此时state=0,将AQS的head属性设置为node1,并将node0和node1之间的next和prev属性置为null,相当于将node0剔除,至此t1获得锁。
t2获得锁的流程与t1一样。



