一.什么是AQS?
1.AQS,AbstractQueuedSynchronizer,抽象队列同步器,是一个用于构建锁和同步器的框架。
2.基于AQS实现的锁或同步器有:可重入锁ReentrantLock、计时锁CountDownLatch、信号量Semaphore等
3.AQS三大核心
(1)state:用整型变量state来标记共享变量的状态,多线程通过操作state来实现锁或同步器机制,state需要用volatile修饰。
(2)queue:AQS内部维护一个基于FIFO算法和双向链表实现的队列。若线程请求共享变量失败,则会被包装成一个Node节点,存到队列尾部。当共享变量空闲,则会唤醒队列的第一个线程去操作变量。
(3)CAS:多线程操作state是通过CAS操作完成的。
4.AQS的两种模式:独占锁和共享锁
(1)独占锁:共享变量只能被一个线程获取到。state初值为0,表示无锁状态,线程可以使用;当线程获取到锁,则通过CAS操作对state加1,表示变量被上锁,其他线程不能使用;当state为0时会唤醒队列中的第一个线程。
(2)共享锁:共享变量可以被多个线程获取到。state初值为N,表示可以被N个线程使用;当线程获取到锁,则通过CAS操作对state减1;当state为0时,其他线程陷入阻塞;当有一个线程释放锁,就会唤醒队列中的第一个线程。
二.synchronized和Lock的区别
1.语法层面
(1)synchronized是关键字,源码在jvm中,由c++实现
(2)Lock是jdk提供的一个接口,由java实现
2.释放锁的方式
(1)synchronized在执行完代码块或者执行发生异常时,会自动释放锁
(2)Lock在执行完代码或者执行发生异常时,都不会主动释放锁,必须调用unlock()手动释放锁
3.功能层面
synchronized和Lock都属于悲观锁,实现了锁机制基本的互斥、同步、锁重入功能;但Lock锁又具有额外的功能
(1)公平锁:可以选择将锁设置为公平锁,即线程按照等待顺序依次获取锁。而synchronized是所有线程一同争抢锁。
(2)可打断:如果线程尝试用lockInterrupibly()获取锁,则线程在等待锁的过程中可以调用interrupt()打断。而synchronized如果在线程等待锁的过程中调用interrupt()打断,会抛出异常。
(3)计时等待:线程使用tryLock()方法获取锁
a.tryLock()是一个非阻塞方法,如果线程获取到锁,则返回true;如果线程获取不到锁,则直接返回false,不会阻塞等待。
b.tryLock(long time,TimeUnit unit)用于计时等待,如果线程获取到锁,则返回true;如果线程获取不到锁,会进入计时等待;若在等待时间内获取到锁,则返回true;若超时仍获取不到锁,则返回false。
c.通过tryLock()方式获取锁,既能知道线程是否获取到锁,还能避免线程陷入长时间阻塞。而synchronized会永久阻塞获取不到锁的线程,且无法知道当前线程是否获取到锁。
(4)多条件等待:可以通过Lock提供的newCondition()方法创建多个条件;线程在获取到锁后,可以通过调用Condition()的await()方法进入等待状态,此时会释放锁;其他线程在获取到锁后,可以通过调用Condition()的signal()方法唤醒因当前条件而进入等待的线程。Lock可以创建多个Condition,对不同线程进行灵活地等待与唤醒。而synchronized只能通过唯一的锁对象来关联等待与唤醒线程。
三.ReentrantLock的实现原理
1.ReentrantLock是支持重入的独占锁,基于AQS框架和Lock接口实现。
2.ReentrantLock提供了公平锁和非公平锁两种模式:公平锁模式下,所有线程会按照等待的先后顺序依次获取锁;非公平锁模式下,对于新进入的线程,会直接通过CAS操作尝试获取锁,CAS操作失败才进入队尾排队。
3.当一个线程想要获取锁:
(1)通过CAS操作修改锁的状态值state,如果操作成功则获取锁
(2)如果CAS操作失败,说明锁被其他线程持有,当前线程进入等待
4.当一个线程想要释放锁:
(1)通过CAS操作修改锁的状态值state
(2)唤醒队列的第一个线程
(3)将该线程从队列移除
5.ReentrantLock支持可重入的特性,即同一线程可以多次获取锁而不陷入阻塞。线程每重入一次锁,就通过CAS对state加1;每释放一次锁就通过CAS对state减1。只有当state为0时才唤醒下一个线程。