死磕多线程(8)-ReentrantLock,Condition详解

ReentrantLock(Lock中使用频率最高的类)-可重入锁
内建锁隐式支持重入性,synchronized通过获取自增,释放自减的方式实现重入。

一、重入性实现原理
重入性锁的特点:线程获取锁的时候,如果已经获取锁的线程是当前线程直接再次获取
由于锁会被获取N次,因此锁只有被释放N次之后才算真正释放成功
如何实现可重入?
以非公平锁为例,判断当前线程能否获得锁为例,核心方法为nonfairTryAcquire:

final boolean nonfairTryAcquire(int acquires) {
    //拿到当前线程
final Thread current = Thread.currentThread();
//获取当前同步状态
int c = getState();
// 1.如果该锁未被任何线程占有,当前线程使用CAS尝试获取同步状态
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 2.此时同步状态不为0,表示已经有线程获取到了同步状态
//判断持有线程是否为当前线程
else if (current == getExclusiveOwnerThread()) {
// 3.若是当前线程,同步状态再次+1
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//将再次+1后的状态写回内存
setState(nextc);
return true;
}
return false;
}

锁的释放:

protected final boolean tryRelease(int releases) {
// 1.同步状态-1
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
// 2.只有当同步状态为0时,锁成功释放,返回false
free = true;
setExclusiveOwnerThread(null);
}
// 3.锁未被完全释放,返回false
setState(c);
return free;
}

二、公平锁与非公平锁
公平锁:锁的获取顺序一定满足时间上的绝对顺序,等待时间最长的线程一定最先获取到锁。
非公平锁:等待时间最长的线程不一定先获取到锁
ReentrantLock默认使用非公平锁:

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

可传入一个boolean值,true时为公平锁,false时为非公平锁:

public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}

1.非公平锁nonFairSync:

final void lock() {
    //不在队列中的线程可能会直接获取到锁
    if(compareAndSetState(0,1))
    setExclusiveOwnerThread(Thread.currentThread());
    else
    acquire(1);
}

final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//-------------------------------------------------
// 1.如果该锁未被任何线程占有,该锁能被当前线程获取
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
//------------------------------------------------
}
// 2.若被占有,检查占有线程是否是当前线程
else if (current == getExclusiveOwnerThread()) {
// 3.再次获取,计数+1
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

2.公平锁FairSync:

final void lock(){
    //少了一次CAS过程
    acquire(1);
}

protected final boolean tryAcquire(int acquires) {
            if (c == 0) {
            	// 增加了!hasQueuedPredecessors()
            	// 当同步队列中存在非空节点,当前线程直接封装为Node节点排队
                if (!hasQueuedPredecessors() &&
                //hasQueuedPredecessors() :实现公平锁,先检测队列中是否有非空节点,没有CAS竞争
                  compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
        }   

3.公平锁与非公平锁对比:
公平锁保证每次获取锁均为同步队列的第一个节点,保证了请求资源时间上的绝对顺序,但是效率较低,需要频繁的进行上下文切换
非公平锁会降低性能开销,在恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟。而且,非公平锁能更充分的利用cpu的时间片,尽量的减少cpu空闲的状态时间。但是可能导致其他线程永远无法获取到锁,造成线程"饥饿"现象。
通常来讲,没有特定的公平性要求尽量选择非公平锁(ReentrantLock默认选择)

三、ReentrantReadWriteLock(可重入读写锁)
读写者模型:
读写锁允许同一时刻被多个读线程访问,
但是在写线程访问时,所有的读线程以及其他写线程均会被阻塞。

1.写锁详解-独占锁
写锁的获取-tryAcquire(int acquires)

protected final boolean tryAcquire(int acquires) {
            /*
             * Walkthrough:
             * 1. If read count nonzero or write count nonzero
             *    and owner is a different thread, fail.
             * 2. If count would saturate, fail. (This can only
             *    happen if count is already nonzero.)
             * 3. Otherwise, this thread is eligible for lock if
             *    it is either a reentrant acquire or
             *    queue policy allows it. If so, update state
             *    and set owner.
             */
            Thread current = Thread.currentThread();
            // 获取读写锁状态
            int c = getState();
            // 获取独占式锁状态-即写锁状态
            int w = exclusiveCount(c);//当前写锁的获取次数
            if (c != 0) {
                // 表示当前有读线程拿到读锁,写线程无法获取同步状态
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                // 写锁的可重入次数已达最大值
                if (w + exclusiveCount(acquires) > MAX_COUNT)//左移16位-1(乘2的10次方)
                    throw new Error("Maximum lock count exceeded");
                // 写锁可重入
                setState(c + acquires);
                return true;
            }
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            // 此时读写状态为0,写锁可以正常获取到同步状态
            // 将当前线程置为只有写锁线程
            setExclusiveOwnerThread(current);
            return true;
        }


写锁的释放通过重写AQS的tryRelease方法,源码为:

protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 同步状态减去写状态
int nextc = getState() - releases;
// 当前写状态是否为0,为0则释放写锁
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
// 不为0则更新同步状态
setState(nextc);
return free;
}

2.读锁-共享锁(一般与独占锁搭配使用实现读写者模型)
读锁获取:
只要当前没有写线程获取到写锁并且读锁的获取次数不超过最大值,读锁就能获取成功。
读锁的释放:减到0彻底将锁释放
读写锁的应用场景:缓存的实现
写锁的降级:写锁可以降级为读锁,但是读锁不能升级为写锁。

注意:读锁 != 无锁:(写锁时所有读线程也停止,读锁可以限制个数)

3.如何区分读状态与写状态:
同步状态的高16位表示读锁获取次数;同步状态低16位表示写锁获取次数。
死磕多线程(8)-ReentrantLock,Condition详解_第1张图片
四、Condition的await与signal 等待/通知机制
Object的wait与notify是与内建锁(对象监视器)搭配使用,完成线程的等待与通知机制。Condition的await、signal是与Lock体系配合实现线程的等待与通知(Java语言层面实现,具有更高的控制与扩展性。)

1.Condition有以下三个独有特性(内建锁不具备)
I.Condition await支持不响应中断,而Object提供wait不支持。(Lock支持响应中断)
II.Condition支持多个等待队列,而Object wait只有一个等待队列。
III.Condition支持设置截止时间,而Object wait只支持设置超时时间。

2.等待方法:await()
I.void await() throws InterruptedException(同wait());当前线程进入等待状态,如果在等待状态中被中断会抛出被中断异常;
II.void awaitUninterruptibly();特性1,等待过程中不响应中断
III.boolean await(long time, TimeUnit unit) throws InterruptedException;在I的基础上增加了超时等待功能,可以自定义时间单位
IV. boolean awaitUntil(Date deadline) throws InterruptedException;特性3,支持设置截止时间

await实现原理:

public final void await() throws InterruptedException {
	        // 判断中断
            if (Thread.interrupted())
                throw new InterruptedException();
============// 将当前线程封装为Node入等待队列===============================
            Node node = addConditionWaiter();
            // .释放当前线程所占用的lock,释放后会唤醒同步队列中的下一个节点
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            // 
            while (!isOnSyncQueue(node)) {
            	// 当线程不在同步队列后将其阻塞,置为WAIT状态
                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);
        }

如何将当前线程插入等待队列 addConditionWaiter()


        private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            if (t != null && t.waitStatus != Node.CONDITION) {
            	// 清空所有等待队列中状态不为Condition的节点
                unlinkCancelledWaiters();
                // 将最新的尾节点赋值
                t = lastWaiter;
            }
            // 将当前线程包装为Node节点且状态为Condition
            Node node = 
            new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            // 尾插入等待队列
            lastWaiter = node;
            return node;
        }

将线程包装为Node节点尾插入等待队列后,线程释放锁过程fullyRelease()


final int fullyRelease(Node node) {
        boolean failed = true;
        try {
        	// 获取当前同步状态
            int savedState = getState();
            // 调用AQS的释放同步状态方法release()
            if (release(savedState)) {
                failed = false;
                return savedState;//释放成功状态为0
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
        	// 若在释放过程中出现异常,将当前节点取消
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

线程如何能从await()方法中退出?


while (!isOnSyncQueue(node)) {
				// 阻塞在此处
                LockSupport.park(this);
    if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
               break;
     }
I.在等待时被中断,通过break退出循环
II.被唤醒后置入同步队列,退出循环。

调用condition.await方法的线程必须是已经获得了lock,也就是当前线程是同步队列中的头结点。调用该方法后会使得当前线程所封装的Node尾插入到等待队列中

  1. signal(),signalAll()
    将等待队列中等待时间最长的节点移动到同步队列中
public final void signal() {
    //判断当前节点是否拿到锁
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            // 拿到当前等待队列的头结点
            Node first = firstWaiter;
            if (first != null)
            	// 唤醒头结点
                doSignal(first);
        }


        private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
// 将头结点从等待队列中移除
first.nextWaiter = null;
// transferForSignal方法对头结点做真正的处理
} while (!transferForSignal(first) && (first = firstWaiter) != null);
}      

final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
// 首先将节点状态更新为0
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
// 将节点使用enq方法尾插到同步队列中
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}

同步队列中的节点置入等待队列:同步队列的头节点作为等待队列的最后一个节点唤醒:唤醒等待队列的头节点,放入同步队列最后一个节点
signalAll()方法:
将等待队列中的每一个节点都移入到同步队列中,即“通知”当前调用condition.await()方法的每一个线程

4.Condition等待队列
Condition队列与AQS中的同步队列共享节点(Node)数据结构,带有头尾指针的单向队列
死磕多线程(8)-ReentrantLock,Condition详解_第2张图片
每当调用lock.newCondition()就会在绑定的lock锁上新增一个等待队列。(特性2)
死磕多线程(8)-ReentrantLock,Condition详解_第3张图片

你可能感兴趣的:(死磕多线程(8)-ReentrantLock,Condition详解)