JUC 并发同步工具类使用详解

 

JUC 并发同步工具类使用详解 

 

  

1、简介

在java1.5被引入的 CountDownLatch,CyclicBarrier、Semaphore、ConcurrentHashMap 和 BlockingQueue 等工具类。存在于 java.util.cucurrent 包下。

 

2、countDownLatch

countDownLatch 这个类使一个线程等待其他线程各自执行完毕后再执行。是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了

countDownLatch类中只提供了一个构造器:

//参数count为计数值
public CountDownLatch(int count) {  };  

类中有三个方法是最重要的:

//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public void await() throws InterruptedException { };   
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  
//将count值减1
public void countDown() { };  

示例:

public class CountDownLatchTest {

    public static void main(String[] args) {
        final CountDownLatch latch = new CountDownLatch(2);
        System.out.println("主线程开始执行…… ……");
        //第一个子线程执行
        ExecutorService es1 = Executors.newSingleThreadExecutor();
        es1.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                    System.out.println("子线程:"+Thread.currentThread().getName()+"执行");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
// 计数-- latch.countDown(); } }); es1.shutdown();
//第二个子线程执行 ExecutorService es2 = Executors.newSingleThreadExecutor(); es2.execute(new Runnable() { @Override public void run() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("子线程:"+Thread.currentThread().getName()+"执行"); latch.countDown(); } }); es2.shutdown(); System.out.println("等待两个线程执行完毕…… ……"); try {
// 等待两个线程都执行完毕 latch.await(); }
catch (InterruptedException e) { e.printStackTrace(); } System.out.println("两个子线程都执行完毕,继续执行主线程"); } }

 

 3、CyclicBarrier 

CyclicBarrier 是 Java 并发编程中一个非常实用的同步工具类。它允许一组线程互相等待,直到所有线程都到达一个共同的屏障点后,再继续执行。这与 CountDownLatch 有些相似,但有一个关键区别:CyclicBarrier 是可以循环使用的,而 CountDownLatch 是一次性的。

怎么使用 CyclicBarrier?

 构造方法

public CyclicBarrier(int parties)
public CyclicBarrier(int parties, Runnable barrierAction)
  • parties 是参与线程的个数。
  • 第二个构造方法有一个 Runnable 参数,这个参数的意思是最后一个到达线程要做的任务。屏障动作 Runnable 由最后一个到达屏障的线程执行,执行完毕后才唤醒所有线程。这个动作很适合用于更新共享状态或执行下一轮任务的初始化工作。

 重要方法

public int await() throws InterruptedException, BrokenBarrierException
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException,
TimeoutException
  • 线程调用 await() 表示自己已经到达栅栏,最后一个到达的线程调用await()方法后,所有在屏障处等待的线程被唤醒,继续执行后续代码。屏障的计数器重置为 n,可以立即开始下一轮的使用。
  • 带超时时间的await()。如果超时,屏障会被置为“损坏”状态,所有其他等待的线程会抛出 BrokenBarrierException

基本使用:

需求:   一个线程组的线程需要等待所有线程完成任务后再继续执行下一次任务

代码实现:

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {

    public static void main(String[] args) {
        // 4个线程参与,所有线程到达屏障后,打印一句话
        int threadCount = 4;
        CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {
            System.out.println("所有线程都已到达屏障,开始执行下一阶段任务!");
        });

        for (int i = 0; i < threadCount; i++) {
            new Thread(new Worker(barrier, i), "Thread-" + i).start();
        }
    }

    static class Worker implements Runnable {
        private final CyclicBarrier barrier;
        private final int id;

        public Worker(CyclicBarrier barrier, int id) {
            this.barrier = barrier;
            this.id = id;
        }

        @Override
        public void run() {
            try {
                // 第一阶段工作
                System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行第一阶段任务...");
                Thread.sleep((long) (Math.random() * 2000)); // 模拟工作耗时
                System.out.println("线程 " + Thread.currentThread().getName() + " 第一阶段完成,等待其他线程...");

                // 等待其他线程完成第一阶段
                barrier.await();

                // 第二阶段工作(所有线程都冲破屏障后,同时开始)
                System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行第二阶段任务...");
                Thread.sleep((long) (Math.random() * 2000));
                System.out.println("线程 " + Thread.currentThread().getName() + " 第二阶段完成。");

            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
}

打印结果:

线程 Thread-0 正在执行第一阶段任务...
线程 Thread-2 正在执行第一阶段任务...
线程 Thread-1 正在执行第一阶段任务...
线程 Thread-3 正在执行第一阶段任务...
线程 Thread-1 第一阶段完成,等待其他线程...
线程 Thread-2 第一阶段完成,等待其他线程...
线程 Thread-0 第一阶段完成,等待其他线程...
线程 Thread-3 第一阶段完成,等待其他线程...
所有线程都已到达屏障,开始执行下一阶段任务!
线程 Thread-3 正在执行第二阶段任务...
线程 Thread-0 正在执行第二阶段任务...
线程 Thread-1 正在执行第二阶段任务...
线程 Thread-2 正在执行第二阶段任务...
线程 Thread-1 第二阶段完成。
...

      从打印结果可以看出,所有线程会等待全部线程到达栅栏之后才会继续执行,并且最后到达的线程会完成 Runnable 的任务。

 CyclicBarrier 使用场景:

        可以用于多线程计算数据,最后合并计算结果的场景。

 CyclicBarrier 与 CountDownLatch 区别:

  • CountDownLatch 是一次性的,CyclicBarrier 是可循环利用的
  • CountDownLatch 参与的线程的职责是不一样的,有的在倒计时,有的在等待倒计时结束。CyclicBarrier 参与的线程职责是一样的。

4、 Semaphore

     Semaphore(信号量)主要用于在一个时刻限制允许的线程数量对共享资源进行并行操作的场景。

通常情况下,使用Semaphore的过程实际上是多个线程获取访问共享资源许可证的过程。Semaphore维护了一个计数器,线程可以通过调用acquire()方法来获取Semaphore中的许可证,当计数器为0时,调用acquire()的线程将被阻塞,直到有其他线程释放许可证;线程可以通过调用release()方法来释放Semaphore中的许可证,这会使Semaphore中的计数器增加,从而允许更多的线程访问共享资源。

Semaphore的应用场景:主要涉及到需要限制资源访问数量或控制并发访问的场景(限流),例如数据库连接、文件访问、网络请求等。
在这些场景中,Semaphore能够有效地协调线程对资源的访问,保证系统的稳定性和性能。
构造器:
  • public Semaphore(int permits):定义Semaphore指定许可证数量(资源数),并且指定非公平的同步器,因此new Semaphore(n)实际上是等价于new Semaphore(n,false)的。
  • public Semaphore(int permits, boolean fair):定义Semaphore指定许可证数量的同时给定非公平或是公平同步器。
方法:
  • acquire():是向Semaphore获取许可证,但是该方法比较偏执一些,获取不到就会一直等(陷入阻塞状态)。
  • tryAcquire(): 尝试向Semaphore获取许可证,如果此时许可证的数量少于申请的数量,则对应的线程会立即返回,结果为false表示申请失败。

  • void release():释放一个许可证,并且在Semaphore的内部,可用许可证的计数器会随之加一,表明当前有一个新的许可证可被使用。

使用注意:Semaphore的设计并未强制要求执行release操作的线程必须是执行了acquire的线程才可以,而是需要开发人员自身具有相应的编程约束来确保Semaphore的正确使用。

 
5、AtomicBoolean, AtomicInteger,AtomicLong,AtomicLongArray, AtomicReference
java.util.concurrent.atomic 的包里有AtomicBoolean, AtomicInteger,AtomicLong,AtomicLongArray, AtomicReference等原子类的类,主要用于在高并发环境下的高效程序处理,来帮助我们简化同步处理.

在Java语言中,++i和i++操作并不是线程安全的,在使用的时候,不可避免的会用到synchronized关键字。而AtomicInteger则使用无锁的方式通过CAS+AQS操作接口。

import java.util.concurrent.atomic.AtomicInteger;
/**
 * 来看AtomicInteger提供的接口。

 //获取当前的值
 
 public final int get()
 
 //取当前的值,并设置新的值
 
  public final int getAndSet(int newValue)
 
 //获取当前的值,并自增
 
  public final int getAndIncrement() 
 
 //获取当前的值,并自减
 
 public final int getAndDecrement()
 
 //获取当前的值,并加上预期的值
 
 public final int getAndAdd(int delta)

 

getAndAdd()方法与AddAndGet方法
AtomicInteger atomicInteger = new AtomicInteger(123);
        System.out.println(atomicInteger.get());  --123

        System.out.println(atomicInteger.getAndAdd(10)); --123 获取当前值,并加10
        System.out.println(atomicInteger.get()); --133

        System.out.println(atomicInteger.addAndGet(10)); --143 获取加10后的值,先加10
        System.out.println(atomicInteger.get()); --143

 

getAndDecrement()和DecrementAndGet()方法

   AtomicInteger atomicInteger = new AtomicInteger(123);
        System.out.println(atomicInteger.get());   --123

        System.out.println(atomicInteger.getAndDecrement()); --123 获取当前值并自减
        System.out.println(atomicInteger.get());  --122

        System.out.println(atomicInteger.decrementAndGet()); --121 先自减再获取减1后的值
        System.out.println(atomicInteger.get()); --121

 

 

 

 

posted @ 2020-03-20 11:38  邓维-java  阅读(418)  评论(0)    收藏  举报