并发工具类

并发工具类

1. CountDownLatch原理

背景:

​ 在日常开发中会遇到这样的场景:需要在主线程中开启多个线程去执行任务,并且主线程需要等待所有子线程执行完后再进行汇总的场景。在CountDownLatch出现之前都是使用线程的join()方法来实现的,但是join()方法不够灵活,不能满足不同场景的需求,所以JDK提供了CountDownLatch这个类。

小结

  1. 相比于join()方法实现线程同步,更灵活、更方便;
  2. CountDownLatch是使用AQS实现的,使用AQS的状态值state来存放计数器的值,首先在初始化时设置状态值(计数器值),当多个线程调用countdown方法时实际时原子性递减AQS的状态值;
  3. 当线程调用await方法后当前线程会被放入AQS的阻塞队列等待计数器为0再返回。其他线程调用countdown方法来让计数器值递减1,当计数器值变为0时,当前线程还要调用AQS的doReleaseShared方法激活由于调用await()方法而被阻塞的线程。
public class CountDownLatchTest {
    private static CountDownLatch cdl = new CountDownLatch(2);
    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(1);
                cdl.countDown();
                System.out.println(2);
                cdl.countDown();
            }
        }).start();
        cdl.await();
        System.out.println(3);
    }
}
//N传入的是线程数
private static CountDownLatch cdl = new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(1);
            cdl.countDown();
        }
    });

    Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(2);
            cdl.countDown();
        }
    });
    t1.start();
    t2.start();
    cdl.await();
    System.out.println(3);
}
  • CountDownLatch的构造函数接收一个int类型的参数作为计数器,如果你想等待N个点完成,这里就传入N。
  • 当我们调用CountDownLatch的countDown方法时,N就会减1,CountDownLatch的await方法会阻塞当前线程,直到N变成零
  • 由于countDown方法可以用在任何地方,所以这里说的N个点,可以是N个线程,也可以是1个线程里的N个执行步骤
  • 用在多个线程时,只需要把这个CountDownLatch的引用传递到线程里即可。

【注意】:计数器必须大于等于0,只是等于0时候,计数器就是零,调用await方法时不会阻塞当前线程CountDownLatch不可能重新初始化或者修改CountDownLatch对象的内部计数器的值。

2. 同步屏障CyclicBarrier

作用:让一个线程到达一个屏障(也可以叫做同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。

CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。

  • 参数是几就最少有几个线程执行cycliBarrier.await()方法;
  • 如果执行await()方法的线程数小于 parties,那么这些执行await()方法的线程就会被阻塞。

CountDownLatch与CyclicBarries的区别

  1. CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置。所以CyclicBarrier能处理更为复杂的业务场景。例如,如果计算发生错误,可以重置计数器,并让线程重新执行一次。
  2. CyclicBarrier还提供其他有用的方法,比如getNumberWaiting方法可以获得Cyclic-Barrier阻塞的线程数量。isBroken()方法用来了解阻塞的线程是否被中断。

3. 信号量Semaphore

作用

Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。

应用场景

Semaphore可以用于做流量控制,特别是公用资源有限的应用场景,比如数据库连接。

使用:

  • Semaphore(int permits)接受一个整型的数字,表示可用的许可证数量。
  • Semaphore(10)表示允许10个线程获取许可证,也就是最大并发数是10。
  • Semaphore的用法也很简单,首先线程使用Semaphore的acquire()方法获取一个许可证,使用完之后调用release()方法归还许可证(锁)。还可以用tryAcquire()方法尝试获取许可证。

方法:

  • int availablePermits():返回此信号量中当前可用的许可证数。
  • int getQueueLength():返回正在等待获取许可证的线程数。
  • boolean hasQueuedThreads():是否有线程正在等待获取许可证。
  • void reducePermits(int reduction):减少reduction个许可证,是个protected方法。
  • Collection getQueuedThreads():返回所有等待获取许可证的线程集合,是个protected方法。

4. 交换数据Exchanger

  1. Exchanger(交换者)是一个用于线程间协作的工具类。
  2. Exchanger用于进行线程间的数据交换
  3. 它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。

应用场景

  • 用于遗传算法:遗传算法里需要选出两个人作为交配对象,这时候会交换两人的数据,并使用交叉规则得出2个交配结果。

  • 用于校对工作;

    public class ExchagerTest {
        private static final Exchanger<String> expr = new Exchanger<>();
        private static ExecutorService pool = Executors.newFixedThreadPool(2);
    
        public static void main(String[] args) {
            pool.execute(new Runnable() {
                @Override
                public void run() {
                    String emp1 = "流水账A";
                    try {
                        expr.exchange(emp1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
    
            pool.execute(new Runnable() {
                @Override
                public void run() {
                    String emp2 = "流水账B";
                    try {
                        String emp1 = expr.exchange(emp2);
                        System.out.println("A和B数据是否一直:"+emp1.equals(emp2));
                        System.out.println("emp1录入的是:"+ emp1);
                        System.out.println("emp2录入的是:"+ emp2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
    		// 关闭线程池
            pool.shutdown();
        }
    }
    /*返回结果:
        A和B数据是否一直:false
        emp1录入的是:流水账A     
        emp2录入的是:流水账B
    */
    

    如果两个线程有一个没有执行exchange()方法,则会一直等待,如果担心有特殊情况发生,避免一直等待,可以使用exchange(V x,longtimeout,TimeUnit unit)设置最大等待时长。

5. LockSupport工具类

LockSupport的主要作用是挂起和唤醒线程,该工具类是创建锁和其他同步类的基础。

LockSupport类与每个使用它的线程都会关联一个许可证,在默认情况下调用LockSupport类的方法的线程是不持有许可证的。需要使用unpark(Thread t)方法添加许可证(使用这个方法,参数为一个线程,表示该线程具有一个许可证)

方法

  1. void park():如果调用该线程的方法已经获取到了许可证,那么就可以立即返回,否则调用线程会被禁止参与线程的调度(被阻塞挂起)
  2. void park(Object blocker):(推荐使用)当线程被阻塞挂起时,blocker对象会被记录到该线程内部。使用诊断工具可以观察线程被阻塞的原因,诊断工具通过调用getBlocker(Thread t)方法来获取blocker对象。参数一般为this
  3. void unspark(Thread t)
    • 当一个线程调用unpark()时,如果参数 t 没有持有 t与LockSpark类关联的许可证,则让线程持有。
    • 若线程 t之前因为调用LockSupport.park()方法而被挂起,则调用unspark()后,该线程会立马被唤醒。
    • 如果线程 t 之前没有调用park,则调用unpark后,再调用park()方法,线程 t 不会被阻塞,会接着执行代码。
import java.util.concurrent.locks.LockSupport;

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("t1线程执行spark()方法");
            //调用park()方法,挂起自己
            LockSupport.park();
            System.out.println("t1线程获得了许可证");
        }
    });
    //启动t1线程
    t1.start();
    //主线程休眠 1sec
    Thread.sleep(1000);
    System.out.println("主线程调用unspark()方法");
    LockSupport.unpark(t1);   //表示t1线程获得了许可证,可以接着从spark()方法后执行代码
}

/*	执行结果
t1线程执行spark()方法
主线程调用unspark()方法
t1线程获得了许可证		*/
  1. void parkNanos(long nanos):如果调用parkNanos方法的线程还没有拿到许可证,则调用线程会被挂起 nanos时间后自动返回(被唤醒,接着执行)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值