AQS全称为AbstractQueuedSynchronizer。其中的设计模板采用了,继承和模板方法设计模式。
独占式获取锁
1.accquire
2.acquireInterruptibly
3.tryAcquireNanos(超时获取)
共享式获取锁
1.acquireShared
2.acquireSharedInterruptibly
3.tryAcquireSharedNanos(超时获取)
独占式的释放锁
1.release
共享式释放锁
1.releaseShared
独占式锁、独占式释放锁、共享式释、共享式释放锁
tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared
其他需要重写的方法
isHeldExclusively(如果同步是独占的,则为true ;否则false)
getState
setState(此操作不具有原子性)
compareAndSetState(具有原子性的更新操作)
所谓的模板方法,就是父类中定义一个框架方法,具体每个流程有子类去实现。说白了就是父类是一个抽象类,父类中不确定的方法交给子类去实现。
/*
* 抽象类,演示短信发送的流程
* */
public abstract class SendCustom {
//短信发给谁
public abstract void to();
//短信谁发的
public abstract void from();
//短信内容
public abstract void content();
//短信发送的类型
public abstract void send();
//发送时间是指当前时间,不需要子类去重写
public void sendDate(){
System.out.println("短信发送时间:"+new Date());
}
//发送短信的模板方法,是不需要子类去重写的。直接调用即可
public void senMessgae(){
from();
to();
sendDate();
content();
send();
}
}
class SendMsg extends SendCustom{
@Override
public void to() {
System.out.println("ms");
}
@Override
public void from() {
System.out.println("yt");
}
@Override
public void content() {
System.out.println("短信发送的内容");
}
@Override
public void send() {
System.out.println("发送的是邮件");
}
public static void main(String[] args) {
SendMsg sendMsg = new SendMsg();
sendMsg.senMessgae();
}
}
实现一个独占锁,必须要实现Lock接口,并且用内部类继承AbstractQueuedSynchronizer重写其isHeldExclusively、tryAcquire、tryRelease方法
/*
* 自己实现一个类似于ReentrantLock独占式锁功能
* 独占锁需要重写:tryAcquire、tryRelease,isHeldExclusively(
* */
/**
* @ClassName: SelfLock
* @Description:实现一个独占锁
*/
public class SelfLock implements Lock {
private Sync sync = new Sync();
//重写aqs
static class Sync extends AbstractQueuedSynchronizer {
//获取锁
@Override
protected boolean tryAcquire(int arg) {
//如果state==0时候,说明锁空闲
if (compareAndSetState(0, 1)) {
//蒋当前线程设置成可访问锁权限
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
//释放锁
@Override
protected boolean tryRelease(int arg) {
//如果状态为1时候,进行释放state=0
if (compareAndSetState(1, 0)) {
//蒋当前线程访问锁权限释放
setExclusiveOwnerThread(null);
return true;
}
return false;
}
//独占锁返回true
@Override
protected boolean isHeldExclusively() {
//如果当前锁已经被占用返回true
return getState() == 1;
}
final ConditionObject conditionObject() {
return new ConditionObject();
}
}
@Override
public void lock() {
sync.acquire(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toMillis(time));
}
@Override
public Condition newCondition() {
return sync.conditionObject();
}
}
class DemoTest {
private SelfLock selfLock = new SelfLock();
public void testSync() {
selfLock.lock();
try {
SleepUtils.sleepSeconds(1);
System.out.println(Thread.currentThread().getName() + "执行中.....");
} finally {
selfLock.unlock();
}
}
public static void main(String[] args) {
DemoTest demoTest = new DemoTest();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
demoTest.testSync();
}).start();
}
}
}
Thread-0---执行中
Thread-1---执行中
Thread-2---执行中
Thread-3---执行中
Thread-4---执行中
Thread-5---执行中
Thread-6---执行中
Thread-7---执行中
Thread-8---执行中
Thread-9---执行中
查看输出结果,可知道selfLockMethods方法锁已经起效了,每次只有一个线程可以拿到锁
简单解释就是说:java在多线程的情况下,其他线程拿不到锁,那么这些线程会被打包成Node然后放进同步队列中。
/*线程等待超时或者被中断了,需要从队列中移除掉*/
static final int CANCELLED = 1;
/*后续的节点等待的状态,当前的节点去通知后面的节点去运行 */
static final int SIGNAL = -1;
/*当前的节点处理等待队列 */
static final int CONDITION = -2;
/*共享,表示状态要想后面的节点进行传播,0表示初始状态*/
static final int PROPAGATE = -3;
上面所有状态值,都赋值与 volatile int waitStatus;
看到上面两个红框,是不是对尾节点采用CAS设置,而首节点不需要感觉疑惑?前面已经说到了一个列子,就是抢夺锁可以是多个线程一起来抢,但释放锁,缺只有一个线程。同样道理,比如线程A、B、C、D四个线程。A已经获取到锁,那么B、C、D线程应该进入同步队列进行等待,但要是BCD三个线程同时执行,这时候,BCD到底谁先在队尾呢?针对这个情况,采用CAS可以避免。
在上面我们自己实现的一个SelfLock锁有一个Lock重写方法,查看acquire方法
@Override
public void lock() {
//加锁=独占锁
sync.acquire(1);
}
public final void acquire(int arg) {
//如果线程没有获取到锁的话,就为当前线程创建节点,并且加入等待队列末尾进行等待
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// tail等待队列的尾部,仅通过方法 enq 修改以添加新的等待节点。
Node pred = tail;
if (pred != null) {
node.prev = pred;
//采用cas方式设置尾部节点,将当前节点,赋值给尾部节点。成功返回当前节点。设置失败会调用enq方法
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
//采用自旋的方式,进行CAS设置头部
for (;;) {
//默认是尾节点
Node t = tail;
//如果没有未节点,说明它是头节点
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
//有尾部节点(t),将尾节点(t)赋值给当前节点(node)头节点(node.prev)返回尾节点
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
/*以独占不间断模式获取已在队列中的线程。由条件等待方法和获取使用。
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取当前节点的前驱节点
final Node p = node.predecessor();
//如果前驱节点是头节点,并且已经拿到锁,将当前节点设置为头节点
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//将线程进行阻塞
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
其中的tryAcquire()是我们SelfLock重写的获取锁的方法
acquire方法里面的意思就是说:如果当前线程已经获取到锁,那么直接返回,如果当前线程未获取到锁,那么会调用addWaiter方法为尝试使用CAS操作给节点设置在队列的尾节点,如果失败了会调用enq方法进行自旋方式,将当前节点插入队列的尾部中。
在上面我们自己实现的锁SelfLock有一个tryRelease方法
@Override
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
//如果锁已经释放了
if (tryRelease(arg)) {
//赋值头节点
Node h = head;
if (h != null && h.waitStatus != 0)
//唤醒节点的后继者
unparkSuccessor(h);
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//获取头节点的后面的一个节点进行唤醒
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
作用:可重入锁、公平和非公平锁。
ReentrantLock的基本实现可以概括为:先通过CAS尝试获取锁。如果此时已经有线程占据了锁,那就加入AQS队列并且被挂起。当锁被释放之后,排在CLH队列队首的线程会被唤醒,然后CAS再次尝试获取锁。在这个时候,如果:
非公平锁:如果同时还有另一个线程进来尝试获取,那么有可能会让这个线程抢先获取;
公平锁:如果同时还有另一个线程进来尝试获取,当它发现自己不是在队首的话,就会排到队尾,由队首的线程获取到锁。
//在ReentrantLock源码212行有非公平锁尝试获取锁的方式
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
点击查看nonfairTryAcquire方法
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取锁的状态,0-锁是被释放,1-锁占用
int c = getState();
//如果锁被释放,那么就立马枪到当前锁,不进入同步队列进行等待,这是与非公平锁的本质上的区别
if (c == 0) {
//cas进行状态state设置成1成功后
if (compareAndSetState(0, acquires)) {
//将当前线程设置成独占访问锁的权限
setExclusiveOwnerThread(current);
return true;
}
}
//如果锁被占用了,并且正好是当前线程占用的话
else if (current == getExclusiveOwnerThread()) {
//对占用锁状态进行累加操作赋值给state
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//赋值给state
setState(nextc);
return true;
}
return false;
}
可重入锁加锁源码其实理解起来很简单,就是判断获取到锁的是不是当前的线程,如果是当前线程的话,进行状态state累加操作,然后通过释放锁的时候,进行自减直到state=0在释放锁。
//代码在231行
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//如果当前锁是释放状态
if (c == 0) {
//hasQueuedPredecessors方法是与非公平锁的主要区别,看下面的源码
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
hasQueuedPredecessors源码,大概意思就是说,如果当前线程有前驱节点,就添加到同步队列的尾部进行排队获取锁,没有前驱节点,那自己当作首节点。
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
protected final boolean tryRelease(int releases) {
//将state-锁的次数赋值给c
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//持有锁是当前线程,并且c=0表示锁已经全部释放结束后,
if (c == 0) {
free = true;
//将线程访问锁的权限释放
setExclusiveOwnerThread(null);
}
//state=0,表示锁未被使用
setState(c);
return free;
}
通过上面源码分析,发现两者本质上的区别就是。非公平锁是属于抢占式锁,只要当前锁是释放的状态,那么等待的线程就可以直接枪到该锁。而公平锁即使在当前锁是释放状态,依然会先去同步队列中判断下是否有前驱节点,如果有的话就将自己加入到尾部,进行等待前驱其他线程执行结束。这样有一个问题就是,在同步队列中的锁会被先挂起然后在被唤醒这一个过程是消耗时间的。而非公平锁缺不存在这个消耗,所以说非公平锁的效率要大于公平锁。
参考:https://www.csdn.net/tags/MtTaYg5sODQ3MzEtYmxvZwO0O0OO0O0O.html
读写锁还存在一个锁降级的概念,就是在释放写锁之前可以将锁降级为读锁,但是读锁不可升级为写锁。锁降级的目的一直都存在争议,但其实锁降级就是一种特殊的可重入操作。存在一直情况,如果当前线程已经持有写锁,但如果当前线程再来获取读锁的话应该是允许的,但如果写锁不释放的话,就算是本身线程来获取读锁也要进行等待,这样是不合理的,所以说它允许锁降级为读锁,这样就可以不用等待直接获取读锁了,也是为了效率的一种体现。
protected final int11 tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
// exclusiveCount(c) != 0 ---》 用 state & 65535 得到低 16 位的值。如果不是0,说明写锁别持有了。
// getExclusiveOwnerThread() != current----> 不是当前线程
// 如果写锁被霸占了,且持有线程不是当前线程,返回 false,加入队列。获取写锁失败。
// 反之,如果持有写锁的是当前线程,就可以继续获取读锁了。
if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
// 获取锁失败
return -1;
// 如果写锁没有被霸占,则将高16位移到低16位。
int r = sharedCount(c);// c >>> 16
// !readerShouldBlock() 和写锁的逻辑一样(根据公平与否策略和队列是否含有等待节点)
// 不能大于 65535,且 CAS 修改成功
if (!readerShouldBlock() && r < 65535 && compareAndSetState(c, c + 65536)) {
// 如果读锁是空闲的, 获取锁成功。
if (r == 0) {
// 将当前线程设置为第一个读锁线程
firstReader = current;
// 计数器为1
firstReaderHoldCount = 1;
}// 如果读锁不是空闲的,且第一个读线程是当前线程。获取锁成功。
else if (firstReader == current) {//
// 将计数器加一
firstReaderHoldCount++;
} else {// 如果不是第一个线程,获取锁成功。
// cachedHoldCounter 代表的是最后一个获取读锁的线程的计数器。
HoldCounter rh = cachedHoldCounter;
// 如果最后一个线程计数器是 null 或者不是当前线程,那么就新建一个 HoldCounter 对象
if (rh == null || rh.tid != getThreadId(current))
// 给当前线程新建一个 HoldCounter ------>详见下图get方法
cachedHoldCounter = rh = readHolds.get();
// 如果不是 null,且 count 是 0,就将上个线程的 HoldCounter 覆盖本地的。
else if (rh.count == 0)
readHolds.set(rh);
// 对 count 加一
rh.count++;
}
return 1;
}
// 死循环获取读锁。包含锁降级策略。
return fullTryAcquireShared(current);
}
共享锁与独占锁的主要区别就是在于state,独占锁的state默认在0(锁被释放),1(锁被使用)之间来回切换。而共享锁,它的state必须是>1的数。比如共享锁的state=10,说明可以有十个线程共享这个锁,并且每个线程得到锁后state-1,如果state=0时候,其他在想拿锁的线程,这个时候会进入同步队列进行等待被唤醒,当线程使用完锁会将state+1,这时候会唤醒同步队列中的等待的线程。
/*
* 实现一个共享锁,并且该锁,可以被指定数量线程同时拿到锁
* */
public class ShareLock implements Lock {
//默认可以同时三个线程拿锁
private final Sync sync = new Sync(3);
public ShareLock() throws IllegalAccessException {
}
static class Sync extends AbstractQueuedSynchronizer {
//赋值默认的初始state,其值表示可以被多少个线程同时拿锁使用
public Sync(int count) throws IllegalAccessException {
if (count <= 0) {
throw new IllegalAccessException("count must large then zero");
}
setState(count);
}
/*
* 获取锁的时候,将state-1;当state<0时候,说明这时候已经没有共享线程已经被使用完。
* 其他线程需要拿锁,需要进入同步队列中等待,源码的放已经明确说明了。当扣减完后,doAcquireShared将线程放入队列
* public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
* */
@Override
protected int tryAcquireShared(int arg) {
//自旋
for (; ; ) {
//获取当前状态
int crrentState = getState();
//获取到锁的时候,将当前state-arg,一般情况一个线程-1
int newState = crrentState - arg;
//如果锁获取成功或者此时state<0时候,返回最新状态
if (newState < 0 || compareAndSetState(crrentState, newState) ) {
return newState;
}
}
}
/*
*
* 释放锁原理就是没释放一个锁,state+1,表示空余出来的位置,可以被其他线程进行使用
* */
@Override
protected boolean tryReleaseShared(int arg) {
for (; ; ) {
int crrentSate = getState();
//一般情一个线程释放锁state-1
int newSate = crrentSate + arg;
if (compareAndSetState(crrentSate, newSate)) {
return true;
}
}
}
final ConditionObject conditionObject() {
return new ConditionObject();
}
}
@Override
public void lock() {
sync.acquireShared(1);
}
@Override
public void unlock() {
sync.releaseShared(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquireShared(1) >= 0;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(time));
}
@Override
public Condition newCondition() {
return sync.conditionObject();
}
}
class TestDemo7 {
private ShareLock shareLock = new ShareLock();
TestDemo7() throws IllegalAccessException {
}
public void test1() {
shareLock.lock();
try {
SleepTools.sencod(1);
System.out.println(Thread.currentThread().getName() + "正在访问test1方法");
} finally {
shareLock.unlock();
}
}
public static void main(String[] args) throws IllegalAccessException {
TestDemo7 testDemo7 = new TestDemo7();
//观察输出结果,没三组输出一次
for (int i = 0; i < 10; i++) {
new Thread(() -> {
testDemo7.test1();
}).start();
}
}
}