Concurrent programming - JUC concurrency tool


Insert image description here

Preface

JUC is a Java concurrent programming tool library that provides some commonly used concurrency tools, such as locks, semaphores, counters, event loops, thread pools, concurrent collections, etc. These tools can help developers simplify the complexity of concurrent programming and improve program efficiency and reliability.

CountDownLatch

CountDownLatch Application

CountDownLatch itself is like a counter that can wait for one or more threads to complete before executing. It is implemented based on AQS.

public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
    
    
    CountDownLatch countDownLatch = new CountDownLatch(3);

    new Thread(() -> {
    
    
        System.out.println("111");
        countDownLatch.countDown();
    }).start();

    new Thread(() -> {
    
    
        System.out.println("222");
        countDownLatch.countDown();
    }).start();

    new Thread(() -> {
    
    
        try {
    
    
            Thread.sleep(1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("333");
        countDownLatch.countDown();
    }).start();

    // 主线会阻塞在这个位置,直到CountDownLatch的state变为0
    countDownLatch.await();
    System.out.println("main");
}

CountDownLatch core source code

// CountDownLatch 的有参构造
public CountDownLatch(int count) {
    
    
    // 健壮性校验
    if (count < 0) throw new IllegalArgumentException("count < 0");
    // 构建Sync给AQS的state赋值
    this.sync = new Sync(count);
}

The countDown method essentially calls AQS's shared lock release operation.

public final boolean releaseShared(int arg) {
    
    
    if (tryReleaseShared(arg)) {
    
    
        // 唤醒在AQS队列中排队的线程。
        doReleaseShared();
        return true;
    }
    return false;
}

// countDownLatch实现的业务
protected boolean tryReleaseShared(int releases) {
    
    
    for (;;) {
    
    
        int c = getState();
        if (c == 0)
            return false;
        // state - 1
        int nextc = c-1;
        // 用CAS赋值
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}
// 如果CountDownLatch中的state已经为0了,那么再次执行countDown跟没执行一样。
// 而且只要state变为0,await就不会阻塞线程。

All functions are provided by AQS. Only the classes that need to be implemented by tryReleaseShared need to be written by themselves.

The await method calls the method provided by AQS to acquire the shared lock and allow interruption.

// await方法
public void await() throws InterruptedException {
    
    
    sync.acquireSharedInterruptibly(1);
}

// AQS获取共享锁并且允许中断的方法
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    
    
    if (Thread.interrupted())
        throw new InterruptedException();
    // countDownLatch操作
    if (tryAcquireShared(arg) < 0)
        // 如果返回的是-1,代表state肯定大于0
        doAcquireSharedInterruptibly(arg);
}

// CountDownLatch实现的tryAcquireShared
protected int tryAcquireShared(int acquires) {
    
    
    // state为0,返回1,。否则返回-1
    return (getState() == 0) ? 1 : -1;
}

// 让当前线程进到AQS队列,排队去
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
    
    
    // 将当前线程封装为Node,并且添加到AQS的队列中
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
    
    
        for (;;) {
    
    
            final Node p = node.predecessor();
            if (p == head) {
    
    
                // 再次走上面的tryAcquireShared,如果返回的是的1,代表state为0
                int r = tryAcquireShared(arg);
                if (r >= 0) {
    
    
                    // 会将当前线程和后面所有排队的线程都唤醒。
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
    
    
        if (failed)
            cancelAcquire(node);
    }
}


Semaphore

Semaphore Application

Semaphore is generally used for flow control. For example, when there is a public resource that can be accessed by multiple threads, Semaphore can be used as a semaphore to limit it. Whenever a thread obtains a connection object, -1 is added to the semaphore, and +1 is added to the semaphore when the thread returns the resource. If the thread finds that the number of resources inside the Semaphore is 0 when taking resources, it will be blocked.

public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
    
    
    // 声明信号量
    Semaphore semaphore = new Semaphore(1);
    // 能否去拿资源
    semaphore.acquire();
    // 拿资源处理业务
    System.out.println("main");
    // 归还资源
    semaphore.release();
}

Semaphore core source code

Semaphore has two ways of competing for resources, fair and unfair.

// 
public Semaphore(int permits, boolean fair) {
    
    
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

// 设置资源个数,State其实就是信号量的资源个数
Sync(int permits) {
    
    
    setState(permits);
}

When calling acquire to acquire resources, it is also based on the method of acquiring shared locks provided by AQS.

Release means adding state + 1 and returning resources.

// 两个一起 阿巴阿巴
public void release() {
    
    
    sync.releaseShared(1);
}

public final boolean releaseShared(int arg) {
    
    
    if (tryReleaseShared(arg)) {
    
    
        // 唤醒在AQS中排队的Node,去竞争资源
        doReleaseShared();
        return true;
    }
    return false;
}

// 信号量实现的归还资源
protected final boolean tryReleaseShared(int releases) {
    
    
    for (;;) {
    
    
        // 拿state
        int current = getState();
        // state + 1
        int next = current + releases;
        // 资源最大值,再+1,变为负数
        if (next < current)
            throw new Error("Maximum permit count exceeded");
        // CAS 改一手
        if (compareAndSetState(current, next))
            return true;
    }
}

After the shared lock releases the resource, if the head node is 0, it cannot be confirmed that there is really no successor node. If the head node is 0, you need to change the status of the head node to -3. When the thread that gets the latest lock resource checks whether there is a successor node and it is a shared lock, wake up the queued thread.

CyclicBarrier

CyclicBarrierApplication

CyclicBarrier is generally called a barrier and is very similar to CountDownLatch. CountDownLatch can only be used once during operation, that is, after the state becomes 0, it cannot be used again. CyclicBarrier can be reused, and its counter can be reset and processed again. And after a problem occurs during the counting process, you can reset the current CyclicBarrier and start the operation again!

public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
    
    
    // 声明栅栏
    CyclicBarrier barrier = new CyclicBarrier(3,() -> {
    
    
        System.out.println("开始!");
    });

    new Thread(() -> {
    
    
        System.out.println("第一位选手到位");
        try {
    
    
            barrier.await();
            System.out.println("第一位往死里跑!");
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }).start();

    new Thread(() -> {
    
    
        System.out.println("第二位选手到位");
        try {
    
    
            barrier.await();
            System.out.println("第二位也往死里跑!");
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }).start();

    System.out.println("裁判已经到位");
    barrier.await();
}

CyclicBarrier core source code

CyclicBarrier does not use AQS directly, but uses ReentrantLock, which uses AQS indirectly.

// CyclicBarrier的有参
public CyclicBarrier(int parties, Runnable barrierAction) {
    
    // 健壮性判断!
    if (parties <= 0) throw new IllegalArgumentException();
    // parties是final修饰的,需要在重置时,使用!
    this.parties = parties;
    // count是在执行await用来计数的。
    this.count = parties;
    // 当计数count为0时 ,先执行这个Runnnable!在唤醒被阻塞的线程
    this.barrierCommand = barrierAction;
}

When the thread executes the await method, it will count-1, and then determine whether the count is 0. If it is not 0, it needs to be added to the Waiter queue of ConditionObject in AQS and park the current thread. If it is 0, it proves that the threads are ready and nextGeneration needs to be executed. All Nodes in the Waiter queue will be transferred to the AQS queue first, and no successor nodes will be set to 0. Then reset the count and broker flags. After unlock is executed, each thread will be awakened.

private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException {
    
    
    // 相当于synchronized中使用wait和notify
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    
    
        // 里面就是boolean,默认false
        final Generation g = generation;

        // 判断之前栅栏加入线程时,是否有超时、中断等问题,如果有,设置boolean为true,其他线程再进来,直接凉凉
        if (g.broken)
            throw new BrokenBarrierException();

        if (Thread.interrupted()) {
    
    
            breakBarrier();
            throw new InterruptedException();
        }


        // 对计数器count--
        int index = --count;
        // 如果--完,是0,代表突破栅栏,干活!
        if (index == 0) {
    
      
            // 默认false
            boolean ranAction = false;
            try {
    
    
                // 如果你用的是2个参数的有参构造,说明你传入了任务,index == 0,先执行CyclicBarrier有参的任务
                final Runnable command = barrierCommand;
                if (command != null)
                    command.run();
                // 设置为true
                ranAction = true;
                nextGeneration();
                return 0;
            } finally {
    
    
                if (!ranAction)
                    breakBarrier();
            }
        }

        // --完之后,index不是0,代表还需要等待其他线程
        for (;;) {
    
    
            try {
    
    
                // 如果没设置超时时间。  await()
                if (!timed)
                    trip.await();
                // 设置了超时时间。  await(1,SECOND)
                else if (nanos > 0L)
                    nanos = trip.awaitNanos(nanos);
            } catch (InterruptedException ie) {
    
    
                if (g == generation && ! g.broken) {
    
    
                    breakBarrier();
                    throw ie;
                } else {
    
    
                    Thread.currentThread().interrupt();
                }
            }

            if (g.broken)
                throw new BrokenBarrierException();

            if (g != generation)
                return index;

            if (timed && nanos <= 0L) {
    
    
                breakBarrier();
                throw new TimeoutException();
            }
        }
    } finally {
    
    
        lock.unlock();
    }
}



// 挂起线程
public final void await() throws InterruptedException {
    
    
    // 允许中断
    if (Thread.interrupted())
        throw new InterruptedException();
    // 添加到队列(不是AQS队列,是AQS里的ConditionObject中的队列)
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
    
    
        // 挂起当前线程
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
}


// count到0,唤醒所有队列里的线程线程
private void nextGeneration() {
    
    
    // 这个方法就是将Waiter队列中的节点遍历都扔到AQS的队列中,真正唤醒的时机,是unlock方法
    trip.signalAll();
    // 重置计数器
    count = parties;
    // 重置异常判断
    generation = new Generation();
}

Summarize

Things to note when using these tool classes:

  • The use of Semaphore should avoid deadlocks and performance problems caused by excessive synchronization.
  • CyclicBarrier's code after the barrier point must ensure that all threads can execute correctly, otherwise some threads may wait forever.
  • The countDown method of CountDownLatch must be called before all threads have completed execution, otherwise some threads may keep waiting.

Selecting the appropriate tool class according to the specific application scenario, correctly using it and designing the concurrency strategy reasonably can improve the efficiency and reliability of the program.

Guess you like

Origin blog.csdn.net/qq_28314431/article/details/133136660