Java并发:Condition详解

微信搜索【程序员囧辉】,关注这个坚持分享技术干货的程序员。

概述

任意一个Java对象,都拥有一组监视器方法(定义在java.lang.Object上),主要包括wait()、wait(long timeout)、notify()以及notifyAll()方法,这些方法与synchronized同步关键字配合,可以实现等待/通知模式。Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式,但是这两者在使用方式以及功能特性上还是有差别的。

 

通过对比Object的监视器方法和Condition接口,可以更详细地了解Condition的特性,对比项与结果如下表。

 

Condition的使用

 

Condition定义了等待/通知两种类型的方法,当前线程调用这些方法时,需要提前获取到Condition对象关联的锁。Condition对象是由Lock对象(调用Lock对象的newCondition()方法)创建出来的,换句话说,Condition是依赖Lock对象的。 Condition的使用方式比较简单,需要注意在调用方法前获取锁。

 

package com.joonwhee.five;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Condition接口与示例
 * 
 * @author JoonWhee
 * @Date 2018年2月11日
 * @param <T>
 */
public class BoundedQueue<T> {

    // 线程池
    private static ExecutorService THREAD_POOL = new ThreadPoolExecutor(2, 4, 60, TimeUnit.MINUTES,
            new LinkedBlockingQueue<Runnable>(1000), Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());

    private Object[] items; // 对象数组
    // 添加的下标,删除的下标和数组当前数量
    private int addIndex, removeIndex, count;
    private Lock lock = new ReentrantLock(); // 定义一个可重入锁
    private Condition notEmpty = lock.newCondition(); // 添加一个Condition
    private Condition notFull = lock.newCondition(); // 添加一个Condition

    public BoundedQueue(int size) {
        items = new Object[size];
    }

    /**
     * 添加一个元素,如果数组满,则添加线程进入等待状态,直到有"空位"
     * @param t
     * @throws InterruptedException
     */
    public void add(T t) throws InterruptedException {
        lock.lock(); // 获取锁
        try {
            while (count == items.length) { // 如果数组满了,notFull进入等待
                System.out.println("items满了,add方法进入等待.");
                notFull.await(); // 等待remove方法里的notFull.signal()
            }

            items[addIndex] = t; // item添加对象
            if (++addIndex == items.length) // 调整数组索引,避免越界
                addIndex = 0;
            ++count; // count+1,代表添加了一个对象
            notEmpty.signal(); // 走到这里,数组里至少有1个对象,必不为空,因此唤醒notEmpty
        } finally {
            System.out.println("add: " + t);
            lock.unlock(); // 释放锁
        }
    }

    /**
     * 由头部删除一个元素,如果数组空,则删除线程进入等待状态,
     * 直到有新添加元素(注意这里并没有真的删除元素,只是把count-1当作是删除)
     * @return
     * @throws InterruptedException
     */
    @SuppressWarnings("unchecked")
    public T remove() throws InterruptedException {
        lock.lock(); // 获取锁
        try {
            while (count == 0) { // 如果数组为空,notEmpty进入等待
                System.out.println("items为空,remove方法进入等待.");
                notEmpty.await(); // 等待add方法里的notEmpty.signal()
            }

            Object x = items[removeIndex]; // item移除对象(假移除)
            if (++removeIndex == items.length) // 调整数组索引,避免越界
                removeIndex = 0;
            --count; // count-1,代表移除了一个对象
            notFull.signal(); // 走到这里,数组里至少有1个空位,必不为满,因此唤醒notFull
            return (T) x;
        } finally {
            System.out.println("remove");
            lock.unlock(); // 释放锁
        }
    }

    public static void main(String args[]) throws InterruptedException {
        int count = 3; // 可以加大数组的size来看更多的过程
        BoundedQueue<Integer> bq = new BoundedQueue<Integer>(count);

        // 开启一个线程执行添加操作
        THREAD_POOL.submit(new Callable<Object>() {
            public Object call() throws InterruptedException {
                for (int i = 0; i < count * 2; i++) {
                    bq.add(i);
                    Thread.sleep(200); // 通过睡眠来制造添加和移除的速度差
                }
                return null;
            }
        });

        // 开启一个线程执行移除操作
        THREAD_POOL.submit(new Callable<Object>() {
            public Object call() throws InterruptedException {
                Thread.sleep(1000);
                for (int i = 0; i < count * 2; i++) {
                    bq.remove();
                    Thread.sleep(50); // 通过睡眠来制造添加和移除的速度差
                }
                return null;
            }
        });
    }
}

输出如下,由于调用remove方法的线程先睡眠了1秒,所以,add方法会先将item数组填满,填满后notFull进入等待。之后,remove方法的线程醒来开始进行移除,当移除之后会唤醒notFull,此时add和remove是并发操作的,但是由于remove的速度更快(通过sleep控制,add每次要睡200毫秒,remove只要50毫秒),所以items必然会被移除到为空,此时notEmpty进入等待,直到add方法往item添加了对象,如此反复。

 


 

重要入口方法

Condition的实现主要包括:条件队列、等待和通知。其中条件队列放的是AQS里的Node数据结构,使用nextWaiter来维护条件队列。等待和通知共有7个方法。

 

  • signal():唤醒该条件队列的头节点。
  • signalAll():唤醒该条件队列的所有节点。
  • awaitUninterruptibly():等待,此方法无法被中断,必须通过唤醒才能解除阻塞。
  • await():当前线程进入等待。
  • awaitNanos(long):当前线程进入等待,有超时时间,入参的单位为纳秒。
  • awaitUntil(Date):当先线程进入等待,直到当前时间超过入参的时间。
  • await(long, TimeUnit):当前线程进入等待,有超时时间,入参可以自己设置时间单位。
这些方法其实大同小异,因此本文只对常用的signal()、signalAll()和await()方法展开详解。搞懂了这3个方法,搞懂其他几个方法也基本没什么阻碍。
 

基础属性

Condition的实现是ConditionObject,而ConditionObject是同步器AbstractQueuedSynchronizer的内部类,因为Condition的操作需要获取相关联的锁,所以作为同步器的内部类也较为合理。每个Condition对象都包含着一个队列(以下称为条件队列),该队列是Condition对象实现等待/通知功能的关键。

 

private static final long serialVersionUID = 1173984872572414699L;
/** First node of condition queue. */
private transient Node firstWaiter; // 条件队列的头节点
/** Last node of condition queue. */    
private transient Node lastWaiter;  // 条件队列的尾节点

/**
 * Creates a new {@code ConditionObject} instance.
 */
public ConditionObject() { }
通过源码可知,条件队列的节点使用的是AQS的Node数据结构。(Node的数据结构见:Node的数据结构

 

另外,由于ConditionObject是AQS的内部类,因此必然和AQS是有很多关联的,因此看本文之前必须先了解AQS的实现原理。(如果你对AQS不熟悉,可以参考我的另一篇文章:Java并发:AbstractQueuedSynchronizer详解(独占模式)

条件队列的基本数据结构如下图中的“条件队列”:
 

await方法

public final void await() throws InterruptedException { // 阻塞当前线程,直接被唤醒或被中断
    if (Thread.interrupted())   // 如果当前线程被中断过,则抛出中断异常
        throw new InterruptedException();
    Node node = addConditionWaiter();   // 添加一个waitStatus为CONDITION的节点到条件队列尾部
    int savedState = fullyRelease(node);    // 释放操作。我们知道只有在拥有锁(acquire成功)的时候才能调用await()方法,因此,调用await()方法的线程的节点必然是同步队列的头节点。所以,当调用await()方法时,相当于同步队列的首节点(获取了锁的节点)移动到Condition的条件队列中。
    int interruptMode = 0;  // 0为正常,被中断值为THROW_IE或REINTERRUPT
    while (!isOnSyncQueue(node)) {  // isOnSyncQueue:判断node是否在同步队列(注意和条件队列区分。调用signal方法会将节点从条件队列移动到同步队列,因此这边就可以跳出while循环)
        LockSupport.park(this); // node如果不在同步队列则进行park(阻塞当前线程)
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)    // 检查线程被唤醒是否是因为被中断,如果是则跳出循环,否则会进行下一次循环,因为被唤醒前提是进入同步队列,所以下一次循环也必然会跳出循环
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)   // acquireQueued返回true代表被中断过,如果中断模式不是THROW_IE,则必然为REINTERRUPT(见上面的checkInterruptWhileWaiting方法)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();   // 移除waitStatus为CANCELLED的节点
    if (interruptMode != 0) // 如果跳出while循环是因为被中断
        reportInterruptAfterWait(interruptMode);    // 则根据interruptMode,选择抛出InterruptedException 或 重新中断当前线程
}
  1. 如果当前线程被中断过,则抛出中断异常。
  2. 调用addConditionWaiter方法(详解见下文addConditionWaiter方法)添加一个waitStatus为CONDITION的节点到条件队列尾部。
  3. 调用fullyRelease方法(详解见下文fullyRelease方法)释放锁。
  4. 调用isOnSyncQueue方法(详解见下文isOnSyncQueue方法)来阻塞线程,直到被唤醒或被中断。
  5. 调用acquireQueued方法(详解见acquireQueued方法详解)来尝试获取锁,并判断线程跳出while循环是被唤醒还是被中断。
  6. 如果跳出while循环是因为被中断,则根据interruptMode,选择抛出InterruptedException 或 重新中断当前线程。
 

addConditionWaiter方法

private Node addConditionWaiter() { // 添加一个waitStatus为CONDITION的节点到条件队列尾部
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();   // 移除waitStatus不为CONDITION的节点(条件队列里的节点waitStatus都为CONDITION)
        t = lastWaiter; // 将t赋值为移除了waitStatus不为CONDITION后的尾节点(上面进行了移除操作,因此尾节点可能会发生变化)
    }
    Node node = new Node(Thread.currentThread(), Node.CONDITION);   // 以当前线程新建一个waitStatus为CONDITION的节点
    if (t == null)  // t为空,代表条件队列为空
        firstWaiter = node; // 将头节点赋值为node
    else
        t.nextWaiter = node;    // 否则,队列不为空。将t(原尾节点)的后继节点赋值为node
    lastWaiter = node;  // 将node赋值给尾节点,即将node放到条件队列的尾部。这里没有用CAS来保证原子性,原因在于调用await()方法的线程必定是获取了锁的线程,也就是说该过程是由锁来保证线程安全的
    return node;
}
  1. 如果条件队列的尾节点不为null并且waitStatus不为CONDITION,则调用unlinkCancelledWaiters方法(详解见下文unlinkCancelledWaiters方法)移除waitStatus不为CONDITION的节点(条件队列里的节点waitStatus都为CONDITION),并将t赋值为移除了waitStatus不为CONDITION后的尾节点(上面进行了移除操作,因此尾节点可能会发生变化)
  2. 以当前线程新建一个waitStatus为CONDITION的节点。
  3. 如果t为空,代表条件队列为空,将头节点赋值为node;否则,队列不为空。将t(原尾节点)的后继节点赋值为node。
  4. 最后将node赋值给尾节点,即将node放到条件队列的尾部。这里没有用CAS来保证原子性,原因在于调用await()方法的线程必定是获取了锁的线程,也就是说该过程是由锁来保证线程安全的。
 

unlinkCancelledWaiters方法

private void unlinkCancelledWaiters() { // 从条件队列移除所有waitStatus不为CONDITION的节点
    Node t = firstWaiter; // t赋值为条件队列的尾节点  
    Node trail = null;
    while (t != null) {
        Node next = t.nextWaiter;   // 向下遍历
        if (t.waitStatus != Node.CONDITION) {   // 如果t的waitStatus不为CONDITION
            t.nextWaiter = null;    // 断开t与t后继节点的关联
            if (trail == null)  // 如果trail为null,则将firstWaiter赋值为next节点,此时还没有遍历到waitStatus为CONDITION的节点,因此直接移动firstWaiter的指针即可移除前面的节点
                firstWaiter = next; 
            else
                trail.nextWaiter = next;    // 否则将trail的后继节点设为next节点。此时,trail节点到next节点中的所有节点被移除(包括t节点,但可能不止t节点。因为,trail始终指向遍历过的最后一个waitStatus为CONDITION,因此只需要将trail的后继节点设置为next,即可将trail之后到next之前的所有节点移除)
            if (next == null)
                lastWaiter = trail;
        }
        else
            trail = t;  // 如果t的waitStatus为CONDITION,则将trail赋值为t,trail始终指向遍历过的最后一个waitStatus为CONDITION
        t = next;   // t指向下一个节点
    }
}
  1. 将t赋值为条件队列的尾节点 。
  2. 从t开始遍历整个条件队列。
  3. 如果t的waitStatus不为CONDITION,则断开t与t后继节点的关联。
  4. 如果trail为null,则将firstWaiter赋值为next节点,此时还没有遍历到waitStatus为CONDITION的节点,因此直接移动firstWaiter的指针即可移除前面的节点。
  5. 如果trail不为null,则将trail的后继节点设为next节点。此时,trail节点到next节点中的所有节点被移除(包括t节点,但可能不止t节点。因为,trail始终指向遍历过的最后一个waitStatus为CONDITION,因此只需要将trail的后继节点设置为next,即可将trail之后到next之前的所有节点移除)
  6. 如果t的waitStatus为CONDITION,则将trail赋值为t,trail始终指向遍历过的最后一个waitStatus为CONDITION。
  7. 最后将 t指向下一个节点,准备开始下一次循环。
例子图解过程:
 

fullyRelease方法

final int fullyRelease(Node node) { // 释放锁
    boolean failed = true;
    try {
        int savedState = getState();    // 当前的同步状态
        if (release(savedState)) {  // 独占模式下release(一般指释放锁)
            failed = false;
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;   // 如果release失败则将该节点的waitStatus设置为CANCELLED
    }
}
调用release方法(详解见release方法详解)释放锁,如果释放失败,则将该节点的waitStatus设置为CANCELLED。
 

isOnSyncQueue方法

final boolean isOnSyncQueue(Node node) {    // 判断node是否再同步队列中
    if (node.waitStatus == Node.CONDITION || node.prev == null) // 如果waitStatus为CONDITION 或 node没有前驱节点,则必然不在同步队列,直接返回false
        return false;
    if (node.next != null) // If has successor, it must be on queue 如果有后继节点,必然是在同步队列中,返回true
        return true;
    /*
     * node.prev can be non-null, but not yet on queue because
     * the CAS to place it on queue can fail. So we have to
     * traverse from tail to make sure it actually made it.  It
     * will always be near the tail in calls to this method, and
     * unless the CAS failed (which is unlikely), it will be
     * there, so we hardly ever traverse much.
     */
    return findNodeFromTail(node);  // 返回node是否为同步队列节点,如果是返回true,否则返回false
}
返回node是否为同步队列节点,如果是返回true,否则返回false。因为只有该节点的线程被唤醒(signal())才会从条件队列移到同步队列。
 

findNodeFromTail方法

private boolean findNodeFromTail(Node node) {   // 从同步队列的尾节点开始向前遍历,如果node为同步队列节点则返回true,否则返回false
    Node t = tail;
    for (;;) {
        if (t == node)
            return true;
        if (t == null)
            return false;
        t = t.prev;
    }
}
从同步队列的尾节点开始向前遍历,如果node为同步队列节点则返回true,否则返回false。
 

signal方法

public final void signal() {
    if (!isHeldExclusively())   // 检查当前线程是否为独占模式同步器的所有者
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;   
    if (first != null)
        doSignal(first);    // 唤醒条件队列的头节点
}
  1. 检查当前线程是否为独占模式同步器的所有者,在ReentrantLock中即检查当前线程是否为拥有锁的线程。如果不是,则抛IllegalMonitorStateException。
  2. 拿到条件队列的头节点,如果不为null,则调用doSignal方法(详解见下文doSignal方法)唤醒头节点。
 

doSignal方法

private void doSignal(Node first) { // 将条件队列的头节点移到同步队列
    do {
        if ( (firstWaiter = first.nextWaiter) == null)  // 将first节点赋值为first节点的后继节点(相当于移除first节点),如果first节点的后继节点为空,则将lastWaiter赋值为null
            lastWaiter = null; 
        first.nextWaiter = null;    // 断开first节点对first节点后继节点的关联
    } while (!transferForSignal(first) &&   // transferForSignal:将first节点从条件队列移动到同步队列
             (first = firstWaiter) != null);    // 如果transferForSignal失败,并且first节点不为null,则向下遍历条件队列的节点,直到节点成功移动到同步队列 或者 firstWaiter为null
}
  1. 将first节点赋值为first节点的后继节点(相当于移除first节点),如果first节点的后继节点为空,则将lastWaiter赋值为null。
  2. 断开first节点与first节点后继节点的关联。
  3. 调用transferForSignal方法(详解见下文transferForSignal方法)将first节点从条件队列移动到同步队列。
  4. 如果transferForSignal失败,并且first节点的后继节点(firstWaiter)不为null,则向下遍历条件队列的节点,直到节点成功移动到同步队列 或者 first节点的后继节点为null。
 

transferForSignal方法

final boolean transferForSignal(Node node) {    // 将node节点从条件队列移动到同步队列,如果成功则返回true。
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))  // 如果不能更改节点的waitStatus,则表示该节点已被取消,返回false
        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).
     */
    Node p = enq(node); // 否则,调用enq方法将node添加到同步队列,注意:enq方法返回的节点是node的前驱节点
    int ws = p.waitStatus;  // 将ws赋值为node前驱节点的等待状态
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) // 如果node前驱节点的状态为CANCELLED(ws>0) 或 使用CAS将waitStatus修改成SIGNAL失败,则代表node的前驱节点无法来唤醒node节点,因此直接调用LockSupport.unpark方法唤醒node节点
        LockSupport.unpark(node.thread);
    return true;
}
  1. 如果不能更改节点的waitStatus,则表示该节点已被取消,返回false。
  2. 调用enq方法(详解见enq方法详解)将node添加到同步队列,注意:enq方法返回的节点是node的前驱节点。因此,此时p节点为node的前驱节点。
  3. 将ws赋值为node前驱节点(p节点)的waitStatus。
  4. 如果p节点的waitStatus为CANCELLED(ws>0) 或 使用CAS将p节点的waitStatus修改成SIGNAL失败,则代表p节点无法来唤醒node节点,因此直接调用LockSupport.unpark方法唤醒node节点。
 

signalAll方法

public final void signalAll() {
    if (!isHeldExclusively())   // 检查当前线程是否为独占模式同步器的所有者
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignalAll(first); // 唤醒条件队列的所有节点
}
  1. 检查当前线程是否为独占模式同步器的所有者,在ReentrantLock中即检查当前线程是否为拥有锁的线程。如果不是,则抛IllegalMonitorStateException。
  2. 拿到条件队列的头节点,如果不为null,则调用doSignalAll方法(详解见下文doSignalAll方法)唤醒条件队列的所有节点。
 

doSignalAll方法

private void doSignalAll(Node first) {  // 将条件队列的所有节点移到同步队列
    lastWaiter = firstWaiter = null;    // 因为要移除条件队列的所有节点到同步队列,因此这边直接将firstWaiter和lastWaiter赋值为null
    do {
        Node next = first.nextWaiter;   // next赋值为first节点的后继节点      
        first.nextWaiter = null;    // 断开first节点对first节点后继节点的关联
        transferForSignal(first);   // transferForSignal:将first节点从条件队列移动到同步队列
        first = next;   // first赋值为next节点
    } while (first != null);    // 循环遍历,将条件队列的所有节点移动到同步队列
}
  1. 因为要移除条件队列的所有节点到同步队列,因此这边直接将firstWaiter和lastWaiter赋值为null。
  2. next赋值为first节点的后继节点 。
  3. 断开first节点对first节点后继节点的关联
  4. 调用transferForSignal方法(详解见上文transferForSignal方法)将first节点从条件队列移动到同步队列。
  5. first赋值为next节点,准备下一次循环。
  6. 如果first不为null,则进入下一次循环。
 

总结

  1. 调用await和signal方法都需要先获得锁,否则会抛异常。
  2. 调用await方法会新建一个waitStatus为CONDITION、线程为当前线程的节点到条件队列尾部,然后当前线程会释放掉锁,并进入阻塞状态,直到该节点被移到同步队列或者被中断。该节点被移动到同步队列,并不代表该节点线程能立马获得锁,还是需要在同步队列中排队并在必要时候(前驱节点为head)调用tryAcquire方法去获取,如果获取成功则代表获得了锁。
  3. 调用signal方法会将条件队列的头节点移动到同步队列。

参考

AbstractQueuedSynchronizer.ConditionObject源码(JDK 1.8)
《Java并发编程的艺术》

 

 

—————END—————

 

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
第1节你真的了解并发吗? [免费观看][免费观看] 00:27:48分钟 | 第2节理解多线程与并发的之间的联系与区别 [免费观看] 00:11:59分钟 | 第3节解析多线程与多进程的联系以及上下文切换所导致资源浪费问题 [免费观看] 00:13:03分钟 | 第4节学习并发的四个阶段并推荐学习并发的资料 [免费观看] 00:09:13分钟 | 第5节线程的状态以及各状态之间的转换详解00:21:56分钟 | 第6节线程的初始化,中断以及其源码讲解00:21:26分钟 | 第7节多种创建线程的方式案例演示(一)带返回值的方式00:17:12分钟 | 第8节多种创建线程的方式案例演示(二)使用线程池00:15:40分钟 | 第9节Spring对并发的支持:Spring的异步任务00:11:10分钟 | 第10节使用jdk8提供的lambda进行并行计算00:14:22分钟 | 第11节了解多线程所带来的安全风险00:13:16分钟 | 第12节从线程的优先级看饥饿问题00:18:42分钟 | 第13节从Java字节码的角度看线程安全性问题00:25:43分钟 | 第14节synchronized保证线程安全的原理(理论层面)00:13:59分钟 | 第15节synchronized保证线程安全的原理(jvm层面)00:25:03分钟 | 第16节单例问题与线程安全性深入解析00:27:15分钟 | 第17节理解自旋锁,死锁与重入锁00:24:58分钟 | 第18节深入理解volatile原理与使用00:28:30分钟 | 第19节JDK5提供的原子类的操作以及实现原理00:27:10分钟 | 第20节Lock接口认识与使用00:19:54分钟 | 第21节手动实现一个可重入锁00:26:31分钟 | 第22节AbstractQueuedSynchronizer(AQS)详解00:49:04分钟 | 第23节使用AQS重写自己的锁00:31:04分钟 | 第24节重入锁原理与演示00:12:24分钟 | 第25节读写锁认识与原理00:18:04分钟 | 第26节细读ReentrantReadWriteLock源码00:30:38分钟 | 第27节ReentrantReadWriteLock锁降级详解00:13:32分钟 | 第28节线程安全性问题简单总结00:15:34分钟 | 第29节线程之间的通信之wait/notify00:32:12分钟 | 第30节通过生产者消费者模型理解等待唤醒机制00:20:50分钟 | 第31节Condition的使用及原理解析00:17:40分钟 | 第32节使用Condition重写wait/notify案例并实现一个有界队列00:22:05分钟 | 第33节深入解析Condition源码00:21:15分钟 | 第34节实战:简易数据连接池00:24:53分钟 | 第35节线程之间通信之join应用与实现原理剖析00:10:17分钟 | 第36节ThreadLocal 使用及实现原理00:17:41分钟 | 第37节并发工具类CountDownLatch详解00:22:04分钟 | 第38节并发工具类CyclicBarrier 详解00:11:52分钟 | 第39节并发工具类Semaphore详解00:17:27分钟 | 第40节并发工具类Exchanger详解00:13:47分钟 | 第41节CountDownLatch,CyclicBarrier,Semaphore源码解析00:29:57分钟 | 第42节提前完成任务之FutureTask使用00:11:43分钟 | 第43节Future设计模式实现(实现类似于JDK提供的Future)00:19:20分钟 | 第44节Future源码解读00:29:22分钟 | 第45节Fork/Join框架详解00:28:09分钟 | 第46节同步容器与并发容器00:18:44分钟 | 第47节并发容器CopyOnWriteArrayList原理与使用00:15:52分钟 | 第48节并发容器ConcurrentLinkedQueue原理与使用00:31:03分钟 | 第49节Java中的阻塞队列原理与使用00:26:18分钟 | 第50节实战:简单实现消息队列00:11:07分钟 | 第51节并发容器ConcurrentHashMap原理与使用00:38:22分钟 | 第52节线程池的原理与使用00:42:49分钟 | 第53节Executor框架详解00:36:54分钟 | 第54节实战:简易web服务器(一)00:55:34分钟 | 第55节实战:简易web服务器(二)00:24:36分钟 | 第56节JDK8的新增原子操作类LongAddr原理与使用00:17:45分钟 | 第57节JDK8新增锁StampedLock详解00:29:37分钟 | 第58节重排序问题00:23:19分钟 | 第59节happens-before简单概述00:15:17分钟 | 第60节锁的内存语义00:13:54分钟 | 第61节volatile内存语义00:12:04分钟 | 第62节final域的内存语义00:34:07分钟 | 第63节实战:问题定位00:07:48分钟 |
Java并发线程安全集合是指在多线程环境下能够保证数据一致性和线程安全的数据结构。Java提供了许多高并发线程安全集合,包括ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArrayList、CopyOnWriteArraySet等。 ConcurrentHashMap是一个线程安全的哈希表,它允许多个线程同时读取并修改其中的元素。它使用分段锁的方式来实现并发访问,不同的线程可以同时访问不同的分段,从而提高了并发性能。 ConcurrentSkipListMap是一个基于跳表的并发有序映射,它可以提供较好的并发性能,且支持按照键的顺序进行遍历。它的实现是通过通过多层链表实现的,每一层链表中的节点按照键的顺序排列。 ConcurrentSkipListSet是一个基于ConcurrentSkipListMap的并发有序集合,它实现了Set接口,并且保证元素的有序性和线程安全性。 CopyOnWriteArrayList是一个线程安全的ArrayList,它通过每次修改时创建一个新的副本来实现线程安全。虽然在插入和删除操作时需要复制整个数组,但读取操作非常高效,适用于读操作远多于写操作的场景。 CopyOnWriteArraySet是一个线程安全的Set,它是基于CopyOnWriteArrayList实现的。它通过复制整个数组来实现线程安全,保证了元素的唯一性和线程安全。 这些高并发线程安全集合在多线程环境中保证了数据的一致性和线程安全性,能够提高并发性能和效率,适用于高度并发和需要频繁读写的场景。但需要注意的是,并发集合在某些操作上可能会损失一些性能,因此在选择使用时需根据具体需求进行权衡和选择。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员囧辉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值