多线程学习(五)

一、AQS
全称是AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架,特点是:用state属性表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁
1、getState-获取state状态;
2、setState-设置state状态;
3、compareAndSetState-cas机制设置state状态;
4、独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源;
5、提供了基于FIFO的等待队列,类似于Monitor的EntryList;
6、条件变量来实现等待,唤醒机制,支持多个条件变量,类似于Monitor的WaitSet;

子类主要实现这样一些方法(默认抛出UnsupportedOperationException)
	tryAcquire
	tryRelease
	tryAcquireShared
	tryReleaseShared
	isHeldExclusively

基于AQS实现不可重入锁:

package com.concurrent.test;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class TestAqs {

    public static void main(String[] args) {
        MyLock lock = new MyLock();

        new Thread(() -> {
            lock.lock();
            try{
                System.out.println("locking...");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println("unlocking...");
                lock.unlock();
            }
        }, "t1").start();

        new Thread(() -> {
            lock.lock();
            try{
                System.out.println("locking...");
            }finally {
                System.out.println("unlocking...");
                lock.unlock();
            }
        }, "t2").start();
    }

}

// 自定义锁(不可重入锁)
class MyLock implements Lock {

    // 独占锁 同步器类
    class Mysync extends AbstractQueuedSynchronizer{

        @Override
        protected boolean tryAcquire(int arg) {
            if(compareAndSetState(0, 1)){
                // 加上了锁, 并设置owner为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            // 注意二者顺序,volatile后变量增加写屏障,使它之后的值看到前面的最新值
            setExclusiveOwnerThread(null);
            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 {
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }

    @Override // 解锁
    public void unlock() {
        sync.release(1);
    }

    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }
}

二、ReentrantLock原理

在这里插入图片描述
加锁解锁流程
默认非公平实现

public ReentrantLock() {
        sync = new NonfairSync();
}

NonfairSync继承自AQS
非公平锁加锁解锁

final void lock() {
            if (compareAndSetState(0, 1))
            	// 直接竞争锁成功(一次加锁成功)
                setExclusiveOwnerThread(Thread.currentThread());
            else
            	// 加锁失败,走acquire方法
                acquire(1);
        }
        
public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
            	// 获取当前节点的前驱节点
                final Node p = node.predecessor();
                // 若前驱节点为head节点则处于第二位,则再次尝试是否能够获取锁
                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);
        }
    }

--------------------------------------------------------
// 释放锁
// ReentrantLock.unlock
public void unlock() {
        sync.release(1);
    }

// aqs
public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

// ReentrantLock
protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

没有竞争时
在这里插入图片描述

第一个竞争出现时
在这里插入图片描述
Thread-1 执行了
1、cas尝试将state由0改为1,结果失败;
2、进入tryAcquire逻辑,这时state已经是1,结果仍然失败;
3、接下来进入addWaiter逻辑,构造Node队列:

  • 图中黄色三角表示该Node的waitStatus状态,其中0为默认正常状态;
  • Node的创建是懒惰的;
  • 其中第一个Node称为Dummy(哑元)或哨兵,用来占位,并不关联线程;
    当前线程进入acquireQueued逻辑
    1、acquireQueued会在一个死循环中不断尝试获取锁,失败后进入park阻塞;
    2、如果自己是紧邻着head(排第二位),那么再次tryAcquire尝试获取锁,当然这时state仍为1,失败;
    3、进入shouldParkAfterFailedAcquire逻辑,将前驱node,即head的waitStatus改为-1,这次返回false;
    在这里插入图片描述
    4、shouldParkAfterFailedAcquire执行完毕回到acquireQueued,再次tryAcquire尝试获取锁,当然这时state仍为1,失败;
    5、当再次进入shouldParkAfterFailedAcquire时,这时因为其前驱node的waitStatus已经是-1,这次返回true;
    6、进入parkAndCheckInterrupt,Thread-1 park(灰色表示)
    在这里插入图片描述
    再次有多个线程经历上述过程竞争失败,变成这个样子
    在这里插入图片描述
    Thread-0释放锁,进入tryRelease流程,如果成功
  • 设置exclusiveOwnerThread为null;
  • state = 0;
    在这里插入图片描述
    当前队列不为null,并且head的waitStatus=-1,进入unparkSuccessor流程:
    1、 找到队列中离head最近的一个Node(没取消的),unpark恢复其运行,本例中即为Thread-1;
    2、回到Thread-1的acquireQueued流程;
    在这里插入图片描述
    如果加锁成功(没有竞争),会设置
  • exclusiveOwnerThread 为Thread-1,state = 1;
  • head指向刚刚Thread-1所在的Node,该Node清空Thread;
  • 原本的head因为从链表断开,而可被垃圾回收;
    如果这时候有其他线程来竞争(非公平的体现),例如这时有Thread-4来了
    在这里插入图片描述
    如果不巧又被Thread-4占了先
  • Thread-4被设置为exclusiveOwnerThread,state = 1;
  • Thread-1再次进入acquireQueued流程,获取锁失败,重新进入park阻塞;

可重入原理

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                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");
                setState(nextc);
                return true;
            }
            return false;
        }

 protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            // 支持锁重入,只有state减为0,才释放成功
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

可打断原理
不可打断模式:
在此模式下,即使它被打断,仍会驻留在AQS队列中,等获得锁后方能继续运行(是继续运行!只是打断标记被设置为true)。

private final boolean parkAndCheckInterrupt() {
		// 如果打断标记已经是true, 则park会失效
        LockSupport.park(this);
        // interrupted会清楚打断标记(这里保证一个线程可以多次park在这里)
        return Thread.interrupted();
    }

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;
                    // 还是需要获得锁后,才能返回打断状态(因此,在没获取到锁之前,打断是无效的,会多次进入parkAndCheckInterrupt中park)
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    // 如果是因为interrupt被唤醒, 返回打断状态为true
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            // 重新产生一次中断
            selfInterrupt();
    }

可打断模式:

public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        // 如果没有获得到锁, 进入(一)
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

// (一) 可打断的获取锁流程
private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    // 在 park 过程中如果被 interrupt 会进入此
                    // 这时候抛出异常,而不会再次进入for(;;)
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

因此,不可打断只重置打断标记,没获取到锁钱,会再次进入死循环park,所以打断无效,而可打断锁通过抛出异常,不用再进入死循环,从而实现可打断。

公平锁实现原理
非公平锁实现

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            // 如果还没有获得锁
            if (c == 0) {
            	// 尝试用cas获得,这里体现了非公平性:不去检查AQS队列
                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");
                setState(nextc);
                return true;
            }
            // 获取失败,返回调用处
            return false;
        }

公平锁实现

// 与非公平锁主要区别在于tryAcquire方法的实现
protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
            	// 先检查AQS队列中是否有前驱节点,没有才去竞争
                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;
        }

// 队列中是否有节点
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;
        // h != t 时表示队列中有Node
        return h != t &&
        	// (s = h.next) == null表示队列中还没有老二
            ((s = h.next) == null || 
            // 或者队列中老二线程不是此线程
            s.thread != Thread.currentThread());
    }

条件变量实现原理
每个条件变量其实就对应着一个等待队列,其实现类是ConditionObject
await流程
开始Thread-0持有锁,调用await,进入ConditionObject 的 addConditionWaiter流程,创建新的Node状态为 -2 (Node.CONDITION),关联Thread-0,加入等待队列尾部
在这里插入图片描述
接下来进入AQS的 fullyRelease 流程,释放同步器上的锁
在这里插入图片描述
unpark AQS 队列中的下一个节点,竞争锁,假设没有其他竞争线程,那么 Thread-1 竞争成功
在这里插入图片描述
park 阻塞 Thread-0
在这里插入图片描述

public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            // 进入ConditionObject 的 addConditionWaiter流程,创建新的Node状态为 -2 (Node.CONDITION),关联Thread-0,加入等待队列尾部
            Node node = addConditionWaiter();
            // 释放锁
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
            	// 当前线程进入park, 等待被唤醒
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }

final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState();
            // 可重入锁一次释放
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

signal流程
假设 Thread-1 要来唤醒 Thread-0
在这里插入图片描述
进入 ConditionObject 的 doSignal流程,取得等待队列中第一个 Node,即 Thread-0 所在Node
在这里插入图片描述
执行 transferForSignal 流程,将该 Node 加入 AQS 队列尾部,将 Thread-0 的waitStatus 改为0,Thread-3 的waitStatus 改为-1
在这里插入图片描述
Thread-1 释放锁,进入 unlock 流程

public final void signal() {
			// 当前线程是否持有锁
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            // 取conditionObject中第一个节点
            Node first = firstWaiter;
            if (first != null)
            	// 执行doSignal方法
                doSignal(first);
        }

 private void doSignal(Node first) {
            do {
            	// 若队列中只有一个节点,将尾节点也置为空
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            // 节点转移成功,则退出, 转移失败且队列不为空, Node后移, 继续尝试
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }

final boolean transferForSignal(Node node) {
        // 将当前节点的状态由 -2 改为 0
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        // 将当前节点加入 aqs 队列尾部, 并返回当前节点的前驱节点
        Node p = enq(node);
        int ws = p.waitStatus;
        // 若前驱节点waitStatus > 0 或 前驱节点waitStatus修改为-1成功, 则返回成功
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        	// 否则, 进入unpark流程
            LockSupport.unpark(node.thread);
        return true;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值