学习笔记 06 --- JUC锁

学习笔记 06 --- JUC锁

ReadWriteLock和ReentrantReadWriteLock:
JUC包中的锁包括“独占锁”和“共享锁”,ReentrantLock就是典型的独占锁。JUC包中的共享锁包括CountDownLatch, Semaphore, ReentrantReadWriteLock等。
ReentrantReadWriteLock是一种读写锁,读和写表示两个锁,一个和读操作相关的锁称为“共享锁”;另一个与写操作相关的锁,称为“排他锁”。

1、读和读之间不互斥,因为读操作不会有线程安全问题;

2、写和写之间互斥,避免一个写操作影响另外一个写操作,引发线程安全问题;

3、读和写之间互斥,避免读操作的时候写操作修改了内容,引发线程安全问题;

总结起来就是,多个Thread可以同时进行读取操作,但是同一时刻只允许一个Thread进行写入操作

ReadWriteLock函数列表

// 返回用于读取操作的锁。
Lock readLock()
// 返回用于写入操作的锁。
Lock writeLock()

 ReentrantReadWriteLock函数列表

// 创建一个新的 ReentrantReadWriteLock,默认是采用“非公平策略”。
ReentrantReadWriteLock()
// 创建一个新的 ReentrantReadWriteLock,fair是“公平策略”。fair为true,意味着公平策略;否则,意味着非公平策略。
ReentrantReadWriteLock(boolean fair)

// 返回当前拥有写入锁的线程,如果没有这样的线程,则返回 null。
protected Thread getOwner()
// 返回一个 collection,它包含可能正在等待获取读取锁的线程。
protected Collection<Thread> getQueuedReaderThreads()
// 返回一个 collection,它包含可能正在等待获取读取或写入锁的线程。
protected Collection<Thread> getQueuedThreads()
// 返回一个 collection,它包含可能正在等待获取写入锁的线程。
protected Collection<Thread> getQueuedWriterThreads()
// 返回等待获取读取或写入锁的线程估计数目。
int getQueueLength()
// 查询当前线程在此锁上保持的重入读取锁数量。
int getReadHoldCount()
// 查询为此锁保持的读取锁数量。
int getReadLockCount()
// 返回一个 collection,它包含可能正在等待与写入锁相关的给定条件的那些线程。
protected Collection<Thread> getWaitingThreads(Condition condition)
// 返回正等待与写入锁相关的给定条件的线程估计数目。
int getWaitQueueLength(Condition condition)
// 查询当前线程在此锁上保持的重入写入锁数量。
int getWriteHoldCount()
// 查询是否给定线程正在等待获取读取或写入锁。
boolean hasQueuedThread(Thread thread)
// 查询是否所有的线程正在等待获取读取或写入锁。
boolean hasQueuedThreads()
// 查询是否有些线程正在等待与写入锁有关的给定条件。
boolean hasWaiters(Condition condition)
// 如果此锁将公平性设置为 ture,则返回 true。
boolean isFair()
// 查询是否某个线程保持了写入锁。
boolean isWriteLocked()
// 查询当前线程是否保持了写入锁。
boolean isWriteLockedByCurrentThread()
// 返回用于读取操作的锁。
ReentrantReadWriteLock.ReadLock readLock()
// 返回用于写入操作的锁。
ReentrantReadWriteLock.WriteLock writeLock()
 ReentrantReadWriteLock数据结构

(01) ReentrantReadWriteLock实现了ReadWriteLock接口。ReadWriteLock是一个读写锁的接口,提供了"获取读锁的readLock()函数" 和 "获取写锁的writeLock()函数"。
(02) ReentrantReadWriteLock中包含:sync对象,读锁readerLock和写锁writerLock。读锁ReadLock和写锁WriteLock都实现了Lock接口。读锁ReadLock和写锁WriteLock中也都分别包含了"Sync对象",它们的Sync对象和ReentrantReadWriteLock的Sync对象 是一样的,就是通过sync,读锁和写锁实现了对同一个对象的访问。
(03) 和"ReentrantLock"一样,sync是Sync类型;而且,Sync也是一个继承于AQS的抽象类。Sync也包括"公平锁"FairSync和"非公平锁"NonfairSync。sync对象是"FairSync"和"NonfairSync"中的一个,默认是"NonfairSync"。


获取共享锁---ReadLock

lock()

lock()在ReadLock中,源码如下:

public void lock() {
    sync.acquireShared(1);
}

acquireShared()

Sync继承于AQS,acquireShared()定义在AQS中。源码如下:

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

说明:acquireShared()首先会通过tryAcquireShared()来尝试获取锁。
尝试成功的话,则不再做任何动作(因为已经成功获取到锁了)。
尝试失败的话,则通过doAcquireShared()来获取锁。doAcquireShared()会获取到锁了才返回。

tryAcquireShared()

tryAcquireShared()定义在ReentrantReadWriteLock.java的Sync中,源码如下:

复制代码
protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    // 获取“锁”的状态
    int c = getState();
    // 如果“锁”是“互斥锁”,并且获取锁的线程不是current线程;则返回-1。
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    // 获取“读取锁”的共享计数
    int r = sharedCount(c);
    // 如果“不需要阻塞等待”,并且“读取锁”的共享计数小于MAX_COUNT;
    // 则通过CAS函数更新“锁的状态”,将“读取锁”的共享计数+1。
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        // 第1次获取“读取锁”。
        if (r == 0) { 
            firstReader = current;
            firstReaderHoldCount = 1;
        // 如果想要获取锁的线程(current)是第1个获取锁(firstReader)的线程
        } else if (firstReader == current) { 
            firstReaderHoldCount++;
        } else {
            // HoldCounter是用来统计该线程获取“读取锁”的次数。
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != current.getId())
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            // 将该线程获取“读取锁”的次数+1。
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}
复制代码

说明:tryAcquireShared()的作用是尝试获取“共享锁”。
如果在尝试获取锁时,“不需要阻塞等待”并且“读取锁的共享计数小于MAX_COUNT”,则直接通过CAS函数更新“读取锁的共享计数”,以及将“当前线程获取读取锁的次数+1”。
否则,通过fullTryAcquireShared()获取读取锁。

1、写锁的线程数C!=0且写锁的线程持有者不是当前线程,返回-1。因为存在锁降级,写线程获取写入锁后可以获取读取锁。

2、依据公平性原则,判断读锁是否需要阻塞,读锁持有线程数小于最大值(65535),且设置锁状态成功,执行以下代码(对于HoldCounter下面再阐述),并返回1。如果不满足改条件,执行fullTryAcquireShared():(HoldCounter部分后面讲解)

fullTryAcquireShared()

fullTryAcquireShared()在ReentrantReadWriteLock中定义,源码如下:

复制代码
final int fullTryAcquireShared(Thread current) {
    HoldCounter rh = null;
    for (;;) {
        // 获取“锁”的状态
        int c = getState();
        // 如果“锁”是“互斥锁”,并且获取锁的线程不是current线程;则返回-1。
        if (exclusiveCount(c) != 0) {
            if (getExclusiveOwnerThread() != current)
                return -1;
        // 如果“需要阻塞等待”。
        // (01) 当“需要阻塞等待”的线程是第1个获取锁的线程的话,则继续往下执行。
        // (02) 当“需要阻塞等待”的线程获取锁的次数=0时,则返回-1。
        } else if (readerShouldBlock()) {
            // 如果想要获取锁的线程(current)是第1个获取锁(firstReader)的线程
            if (firstReader == current) {
            } else {
                if (rh == null) {
                    rh = cachedHoldCounter;
                    if (rh == null || rh.tid != current.getId()) {
                        rh = readHolds.get();
                        if (rh.count == 0)
                            readHolds.remove();
                    }
                }
                // 如果当前线程获取锁的计数=0,则返回-1。
                if (rh.count == 0)
                    return -1;
            }
        }
        // 如果“不需要阻塞等待”,则获取“读取锁”的共享统计数;
        // 如果共享统计数超过MAX_COUNT,则抛出异常。
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // 将线程获取“读取锁”的次数+1。
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            // 如果是第1次获取“读取锁”,则更新firstReader和firstReaderHoldCount。
            if (sharedCount(c) == 0) {
                firstReader = current;
                firstReaderHoldCount = 1;
            // 如果想要获取锁的线程(current)是第1个获取锁(firstReader)的线程,
            // 则将firstReaderHoldCount+1。
            } else if (firstReader == current) {
                firstReaderHoldCount++;
            } else {
                if (rh == null)
                    rh = cachedHoldCounter;
                if (rh == null || rh.tid != current.getId())
                    rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                // 更新线程的获取“读取锁”的共享计数
                rh.count++;
                cachedHoldCounter = rh; // cache for release
            }
            return 1;
        }
    }
}
复制代码

说明:fullTryAcquireShared()会根据“是否需要阻塞等待”,“读取锁的共享计数是否超过限制”等等进行处理。如果不需要阻塞等待,并且锁的共享计数没有超过限制,则通过CAS尝试获取锁,并返回1。

doAcquireShared()

doAcquireShared()定义在AQS函数中,源码如下:

复制代码
private void doAcquireShared(int arg) {
    // addWaiter(Node.SHARED)的作用是,创建“当前线程”对应的节点,并将该线程添加到CLH队列中。
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            // 获取“node”的前一节点
            final Node p = node.predecessor();
            // 如果“当前线程”是CLH队列的表头,则尝试获取共享锁。
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            // 如果“当前线程”不是CLH队列的表头,则通过shouldParkAfterFailedAcquire()判断是否需要等待,
            // 需要的话,则通过parkAndCheckInterrupt()进行阻塞等待。若阻塞等待过程中,线程被中断过,则设置interrupted为true。
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
复制代码

说明:doAcquireShared()的作用是获取共享锁。
它会首先创建线程对应的CLH队列的节点,然后将该节点添加到CLH队列中。CLH队列是管理获取锁的等待线程的队列。
如果“当前线程”是CLH队列的表头,则尝试获取共享锁;否则,则需要通过shouldParkAfterFailedAcquire()判断是否阻塞等待,需要的话,则通过parkAndCheckInterrupt()进行阻塞等待。
doAcquireShared()会通过for循环,不断的进行上面的操作;目的就是获取共享锁。需要注意的是:doAcquireShared()在每一次尝试获取锁时,是通过tryAcquireShared()来执行的!

释放共享锁---ReadLock

释放共享锁的思想,是先通过tryReleaseShared()尝试释放共享锁。尝试成功的话,则通过doReleaseShared()唤醒“其他等待获取共享锁的线程”,并返回true;否则的话,返回flase。

unlock()

public  void unlock() {
    sync.releaseShared(1);
}

说明:该函数实际上调用releaseShared(1)释放共享锁。

 

releaseShared()

releaseShared()在AQS中实现,源码如下:

public final boolean releaseShared( int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

说明:releaseShared()的目的是让当前线程释放它所持有的共享锁。
它首先会通过tryReleaseShared()去尝试释放共享锁。尝试成功,则直接返回;尝试失败,则通过doReleaseShared()去释放共享锁。

 

tryReleaseShared()

tryReleaseShared()定义在ReentrantReadWriteLock中,源码如下:

protected final boolean tryReleaseShared( int unused) {
    // 获取当前线程,即释放共享锁的线程。
    Thread current = Thread.currentThread();
    // 如果想要释放锁的线程(current)是第1个获取锁(firstReader)的线程,
    // 并且“第1个获取锁的线程获取锁的次数”=1,则设置firstReader为null;
    // 否则,将“第1个获取锁的线程的获取次数”-1。
    if (firstReader == current) {
        // assert firstReaderHoldCount > 0;
        if (firstReaderHoldCount == 1)
            firstReader = null;
        else
            firstReaderHoldCount--;
    // 获取rh对象,并更新“当前线程获取锁的信息”。
    } else {
 
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != current.getId())
            rh = readHolds.get();
        int count = rh.count;
        if (count <= 1) {
            readHolds.remove();
            if (count <= 0)
                throw unmatchedUnlockException();
        }
        --rh.count;
    }
    for (;;) {
        // 获取锁的状态
        int c = getState();
        // 将锁的获取次数-1。
        int nextc = c - SHARED_UNIT;
        // 通过CAS更新锁的状态。
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

说明:tryReleaseShared()的作用是尝试释放共享锁。

 

doReleaseShared()

doReleaseShared()定义在AQS中,源码如下:

    for (;;) {
        // 获取CLH队列的头节点
        Node h = head;
        // 如果头节点不为null,并且头节点不等于tail节点。
        if (h != null && h != tail) {
            // 获取头节点对应的线程的状态
            int ws = h.waitStatus;
            // 如果头节点对应的线程是SIGNAL状态,则意味着“头节点的下一个节点所对应的线程”需要被unpark唤醒。
            if (ws == Node.SIGNAL) {
                // 设置“头节点对应的线程状态”为空状态。失败的话,则继续循环。
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;
                // 唤醒“头节点的下一个节点所对应的线程”。
                unparkSuccessor(h);
            }
            // 如果头节点对应的线程是空状态,则设置“文件点对应的线程所拥有的共享锁”为其它线程获取锁的空状态。
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        // 如果头节点发生变化,则继续循环。否则,退出循环。
        if (h == head)                   // loop if head changed
            break;
    }
}

说明:doReleaseShared()会释放“共享锁”。它会从前往后的遍历CLH队列,依次“唤醒”然后“执行”队列中每个节点对应的线程;最终的目的是让这些线程释放它们所持有的锁。


共享锁的公平锁和非公平锁

和互斥锁ReentrantLock一样,ReadLock也分为公平锁和非公平锁。

公平锁和非公平锁的区别,体现在判断是否需要阻塞的函数readerShouldBlock()是不同的。
公平锁的readerShouldBlock()的源码如下:

final boolean readerShouldBlock() {
    return hasQueuedPredecessors();
}

 

在公平共享锁中,如果在当前线程的前面有其他线程在等待获取共享锁,则返回true;否则,返回false。
非公平锁的readerShouldBlock()的源码如下:

final boolean readerShouldBlock() {
    return apparentlyFirstQueuedIsExclusive();
}

在非公平共享锁中,它会无视当前线程的前面是否有其他线程在等待获取共享锁。只要该非公平共享锁对应的线程不为null,则返回true。

获取独占锁---WriteLock

lock()

public void lock() { 
sync.acquire(1); 
} 
acquire()
public final void acquire(int arg) {  
        if (!tryAcquire(arg) &&  
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))  
            selfInterrupt();  
    }  
tryAcquire()

protected final boolean tryAcquire(int acquires) {  
        //当前线程  
        Thread current = Thread.currentThread();  
        //当前锁个数  
        int c = getState();  
        //写锁个数  
        int w = exclusiveCount(c);  
          
        //当前锁个数 != 0(是否已经有线程持有锁),线程重入  
        if (c != 0) {  
            //w == 0,表示写线程数为0  
            //或者独占锁不是当前线程,返回false  
            if (w == 0 || current != getExclusiveOwnerThread())  
                return false;  
              
            //超出最大范围(65535)  
            if (w + exclusiveCount(acquires) > MAX_COUNT)  
                throw new Error("Maximum lock count exceeded");  
            //设置锁的线程数量  
            setState(c + acquires);  
            return true;  
        }  
          
        //是否阻塞  
        if (writerShouldBlock() ||  
            !compareAndSetState(c, c + acquires))  
            return false;  
          
        //设置锁为当前线程所有  
        setExclusiveOwnerThread(current);  
        return true;  
    }  

在tryAcquire()中有一个段代码

int w = exclusiveCount(c);

该段代码主要是获取线程的数量的,在前面的特性里面有讲过读取锁和写入锁的数量最大分别只能是65535(包括重入数)。为何是65535呢?在前面LZ也提到过独占锁ReentrantLock中有一个state,共享锁中也有一个state,其中独占锁中的state为0或者1如果有重入,则表示重入的次数,共享锁中表示的持有锁的数量。而在ReadWriteLock中则不同,由于ReadWriteLock中存在两个锁,他们之间有联系但是也有差异,所以需要有两个state来分别表示他们。于是ReentrantReadWriteLock就将state一分二位,高16位表示共享锁的数量,低16位表示独占锁的数量。2^16 – 1 = 65535。这就是前面提过的为什么读取锁和写入锁的数量最大分别只能是65535。

在上段tryAcquire方法的源代码中,主要流程如下:

1、首先获取c、w。然后判断是否已经有线程持有写锁(c != 0),如果持有,则线程进行重入。若w == 0(写入锁==0)或者 current != getExclusiveOwnerThread()(锁的持有者不是当前线程),则返回false。如果写入锁的数量超出最大范围(65535),则抛出error。

2、如果当且写线程数为0(那么读线程也应该为0,因为上面已经处理c!=0的情况),并且当前线程需要阻塞那么就返回失败;如果通过CAS增加写线程数失败也返回失败。

3、当c ==0或者c>0,w >0,则设置锁的持有则为当前线程。

释放独占锁---WriteLock

unlock()

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

protected final boolean tryRelease(int releases) {  
        //若锁的持有者不是当前线程,抛出异常  
        if (!isHeldExclusively())  
            throw new IllegalMonitorStateException();  
        //写锁的新线程数  
        int nextc = getState() - releases;  
        //若写锁的新线程数为0,则将锁的持有者设置为null  
        boolean free = exclusiveCount(nextc) == 0;  
        if (free)  
            setExclusiveOwnerThread(null);  
        //设置写锁的新线程数  
        setState(nextc);  
        return free;  
    }
写锁的释放过程还是相对而言比较简单的:首先查看当前线程是否为写锁的持有者,如果不是抛出异常。然后检查释放后写锁的线程数是否为0,如果为0则表示写锁空闲了,释放锁资源将锁的持有线程设置为null,否则释放仅仅只是一次重入锁而已,并不能将写锁的线程清空。

总结下读写锁的Acquire和release判断大致流程:

写锁Acquire:

1.获取当前线程,state值和写锁重入次数;
2.如果state不为0,说明锁被占用,可能写锁也可能读锁,需要继续判断;
3.在state不为0情况下,如果写锁的重入为0,说明读锁被占用,因为读锁阻塞写锁,所有返回false;
4.在state不为0情况下,如果写锁的重入不为0,说明写锁被占用,因为可重入,所以判断是否为当前线程,不是false;
5.在3、4判断没问题,那就是当前线程写锁重入,就判断下写锁重入后是否大于最大限制,如达到,异常;
6.如5判断没达到最大线程,那就设置写锁重入次数,返回true,获取成功;
7.如果2判断锁没有被持有,基于队列策略判断写是否需要阻塞(非公平时,写不需要阻塞,公平时判断head->next是否null或非当前线程),需要阻塞返回false,挂起,不需要阻塞就cas操作设置state值;
8.如果7需要阻塞或cas设置失败,返回false,挂起;
9.如果7不需要阻塞且cas成功,设置独占线程,返回true,Acquire成功。

写锁release:

1.首先判断是否当前线程持有,否就异常;
2.计算state释放后的值 ;
3.判断释放后的写锁重入是否为0;
4.如果3为true,写锁重入为0那就设置独占线程为null;
5.最后设置AQS的state值,返回3的判断结果。


读锁Acquire:

1.获取当前线程和state锁持有次数
2.线程持有的写锁可降级为读锁,判断有没有其他线程持有写锁,如有,因为写锁阻塞读锁,那就挂起当前线程;
3.如2没有其他线程持有写锁,说明要不写锁没被占用,要不当前线程持有,那就继续,获取读锁的持有;
4.判断3个条件:
4.1)读释放不需要挂起;非公平时判断是否存在head->next为读线程,公平时判断head->next是否null或非当前线程;
4.2)读锁持有小于最大;
4.3)cas设置读锁持有成功
5.如果4的判断都没有问题,继续判断读锁持有是否为0:
5.1)为0表示首次持有读锁,设置2个首次变量缓存首次持有读线程和首次持有读线程的重入次数,这样处理,如果只有一个读的话,以后就不用去查询缓存;
5.2)如果读锁不为0,说明有线程持有读锁,判断当前线程是否是之前缓存的首次持有读线程,如果是,累加缓存的首次持有读线程的重入次数;
5.3)如果上面2个都不满足,那就从缓存的持有变量取当前线程的持有,然后累加重入次数,Acquire成功
6.如4的条件不满足,那就for循环处理当前线程,处理的流程大致同2、3、4、5:
6.1)先判断是否有写锁,如有继续判断是否其他线程持有,如果其他线程持有,那就挂起;
6.2)如果没有线程持有写锁,那就判断读是否要阻塞,如果需要阻塞,继续判断:
6.2.1)已经获取读锁的重入,即使需要阻塞也不管,转到6.3处理,Acquire成功;
6.2.1)如果是其他线程的首次请求,加上上面又判断需要阻塞了,那就Acquire失败,阻塞;
6.3)上面判断Acquire没问题,判断读的持有是否达到最大,最大那就异常,没有下面处理下一些缓存变量,同5的处理,Acquire成功。

读锁release:

1.取当前线程;
2.判断是否已经持有读锁了:
1)如果是,判断重入次数,为1就直接读锁为null,否则递减重入次数;
2)如果不是,那就从缓存的持有里面取当前线程的重入,如果重入小于等于1,需要从持有缓存remove当前线程,这里有个小于等于0的判断,没搞懂什么场景出现,最后递减;
3.for循环设置读锁的持有次数,返回持有次数跟0的比较值。


readLock在没有被释放的时候可以被多个线程同时读取,此时还可以重入writeLock锁。
writeLock在没有被释放的时候其他锁不能进入,也不能重入readLock锁,直到writeLock被解锁。



(1).重入方面其内部的WriteLock可以获取ReadLock,但是反过来ReadLock想要获得WriteLock则永远都不要想。

(2).WriteLock可以降级为ReadLock,顺序是:先获得WriteLock再获得ReadLock,然后释放WriteLock,这时候线程将保持Readlock的持有。反过来ReadLock想要升级为WriteLock则不可能,为什么?参看(a),呵呵.

(3).ReadLock可以被多个线程持有并且在作用时排斥任何的WriteLock,而WriteLock则是完全的互斥。这一特性最为重要,因为对于高读取频率而相对较低写入的数据结构,使用此类锁同步机制则可以提高并发量。

(4).不管是ReadLock还是WriteLock都支持Interrupt,语义与ReentrantLock一致。

(5).WriteLock支持Condition并且与ReentrantLock语义一致,而ReadLock则不能使用Condition,否则抛出UnsupportedOperationException异常。


CountDownLatch:

CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

CountDownLatch函数列表

构造一个用给定计数初始化的 CountDownLatch。

// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。
void await()
// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。
boolean await(long timeout, TimeUnit unit)
// 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
void countDown()
// 返回当前计数。
long getCount()
// 返回标识此锁存器及其状态的字符串。
String toString()
总结:CountDownLatch是通过“共享锁”实现的。在创建CountDownLatch中时,会传递一个int类型参数count,该参数是“锁计数器”的初始状态,表示该“共享锁”最多能被count给线程同时获取。当某线程调用该CountDownLatch对象的await()方法时,该线程会等待“共享锁”可用时,才能获取“共享锁”进而继续运行。而“共享锁”可用的条件,就是“锁计数器”的值为0!而“锁计数器”的初始值为count,每当一个线程调用该CountDownLatch对象的countDown()方法时,才将“锁计数器”-1;通过这种方式,必须有count个线程调用countDown()之后,“锁计数器”才为0,而前面提到的等待线程才能继续运行!


CyclicBarrier:

CyclicBarrier是一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。

CyclicBarrier函数列表CyclicBarrier是通过ReentrantLock(独占锁)和Condition来实现的。

创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,但它不会在启动 barrier 时执行预定义的操作。
CyclicBarrier(int parties, Runnable barrierAction)
创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动 barrier 时执行给定的屏障操作,该操作由最后一个进入 barrier 的线程执行。

int await()
在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。
int await(long timeout, TimeUnit unit)
在所有参与者都已经在此屏障上调用 await 方法之前将一直等待,或者超出了指定的等待时间。
int getNumberWaiting()
返回当前在屏障处等待的参与者数目。
int getParties()
返回要求启动此 barrier 的参与者数目。
boolean isBroken()
查询此屏障是否处于损坏状态。
void reset()
将屏障重置为其初始状态。


CountDownLatchCyclicBarrier
(01) CountDownLatch的作用是允许1或N个线程等待其他线程完成执行;而CyclicBarrier则是允许N个线程相互等待。
(02) CountDownLatch的计数器无法被重置;CyclicBarrier的计数器可以被重置后使用,因此它被称为是循环的barrier。


Semaphore:

Semaphore是一个计数信号量,它的本质是一个"共享锁"。

信号量维护了一个信号量许可集。线程可以通过调用acquire()来获取信号量的许可;当信号量中有可用的许可时,线程能获取该许可;否则线程必须等待,直到有可用的许可为止。 线程可以通过release()来释放它所持有的信号量许可。

Semaphore的函数列表

Semaphore(int permits)
// 创建具有给定的许可数和给定的公平设置的 Semaphore。
Semaphore(int permits, boolean fair)

// 从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。
void acquire()
// 从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞,或者线程已被中断。
void acquire(int permits)
// 从此信号量中获取许可,在有可用的许可前将其阻塞。
void acquireUninterruptibly()
// 从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞。
void acquireUninterruptibly(int permits)
// 返回此信号量中当前可用的许可数。
int availablePermits()
// 获取并返回立即可用的所有许可。
int drainPermits()
// 返回一个 collection,包含可能等待获取的线程。
protected Collection<Thread> getQueuedThreads()
// 返回正在等待获取的线程的估计数目。
int getQueueLength()
// 查询是否有线程正在等待获取。
boolean hasQueuedThreads()
// 如果此信号量的公平设置为 true,则返回 true。
boolean isFair()
// 根据指定的缩减量减小可用许可的数目。
protected void reducePermits(int reduction)
// 释放一个许可,将其返回给信号量。
void release()
// 释放给定数目的许可,将其返回到信号量。
void release(int permits)
// 返回标识此信号量的字符串,以及信号量的状态。
String toString()
// 仅在调用时此信号量存在一个可用许可,才从信号量获取许可。
boolean tryAcquire()
// 仅在调用时此信号量中有给定数目的许可时,才从此信号量中获取这些许可。
boolean tryAcquire(int permits)
// 如果在给定的等待时间内此信号量有可用的所有许可,并且当前线程未被中断,则从此信号量获取给定数目的许可。
boolean tryAcquire(int permits, long timeout, TimeUnit unit)
// 如果在给定的等待时间内,此信号量有可用的许可并且当前线程未被中断,则从此信号量获取一个许可。
boolean tryAcquire(long timeout, TimeUnit unit)
Semaphore是通过共享锁实现的。根据共享锁的获取原则,Semaphore分为"公平信号量"和"非公平信号量"。

"公平信号量"和"非公平信号量"的区别

"公平信号量"和"非公平信号量"的释放信号量的机制是一样的!不同的是它们获取信号量的机制线程在尝试获取信号量许可时,对于公平信号量而言,如果当前线程不在CLH队列的头部,则排队等候;而对于非公平信号量而言,无论当前线程是不是在CLH队列的头部,它都会直接获取信号量。该差异具体的体现在,它们的tryAcquireShared()函数的实现不同。




**********************************************未完待续*********************************************

该文为本人学习的笔记,方便以后自己查阅。参考网上各大帖子,取其精华整合自己的理解而成。如有不对的地方,请多多指正!自勉!共勉!

**************************************************************************************************


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值