多线程2-工具类

1. 闭锁:CountDownLatch

1.1 使用场景

若有多条线程,其中一条线程需要等到其他所有线程准备完所需的资源后才能运行,这样的情况可以使用闭锁。

CountDownLatch允许一个或者多个线程等待其他线程完成操作。

1.2 代码实现

public class CountDownLatchTest {

    static CountDownLatch latch = new CountDownLatch(3);

    public static void main(String[] args) {
        // 初始化闭锁,并设置资源个
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 加载资源1
            System.out.println("加载资源的代码1...");
            // 本资源加载完后,闭锁-1
            latch.countDown();
        }).start();

        new Thread(() -> {
            // 加载资源2
            System.out.println("加载资源的代码2...");
            // 本资源加载完后,闭锁-1
            latch.countDown();
            System.out.println("加载资源的代码3...");
        }).start();

        new Thread( new Runnable(){
            public void run(){
                // 本线程必须等待所有资源加载完后才能执行
                try {
                    latch.await(6, TimeUnit.SECONDS);
                    // 当闭锁数量为0时,await返回,执行接下来的任务
                    System.out.println("资源加载完毕,执行任务...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } ).start();
    }
}
/**
 执行结果:
 加载资源的代码2...
 加载资源的代码3...
 加载资源的代码1...
 资源加载完毕,执行任务...
 */

countDown方法每调用一次,节点数减一,直到节点数为0时,等待的线程被唤醒。由于countDown可以用在任何地方,所以这里说的N个节点,可以是N个线程,也可以是一个线程里的N个执行步骤。

await还有一个重载方法await(long time, TimeUnit unit),超时不等待

2. 同步屏障:CyclicBarrier

2.1 使用场景

若有多条线程,他们到达屏障时将会被阻塞,只有当所有线程都到达屏障时才能打开屏障,所有线程同时执行,若有这样的需求可以使用同步屏障。此外,当屏障打开的同时还能指定执行的任务。

可以用于多线程计算数据,最后合并计算结果的场景。每个线程计算完毕之后插入一个屏障,都处理完之后在执行一个run方法去处理汇总后的计算结果。

2.2 闭锁 与 同步屏障 的区别

  • 闭锁只会阻塞一条线程,目的是为了让该条任务线程满足条件后执行;
  • 而同步屏障会阻塞所有线程,目的是为了让所有线程同时执行(实际上并不会同时执行,而是尽量把线程启动的时间间隔降为最少)。
  • CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置。

2.3 代码实现

public class BarrierTest {
    // 创建同步屏障对象,并制定需要等待的线程个数 和 打开屏障时需要执行的任务
    static CyclicBarrier barrier = new CyclicBarrier(3,new Runnable(){
        public void run(){
            //当所有线程准备完毕后触发此任务
            System.out.println("准备好了");
        }
    });
    
    public static void main(String[] args) {
        // 启动三条线程
        for( int i=0; i<3; i++ ){
            new Thread( new Runnable(){
                public void run(){
                    // 等待,(每执行一次barrier.await,同步屏障数量-1,直到为0时,打开屏障)
                    try {
                        System.out.println("屏障未打开");
                        barrier.await();
                        System.out.println("屏障打开");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }
            } ).start();
        }
    }
}
/**
 屏障未打开
 屏障未打开
 屏障未打开
 准备好了
 屏障打开
 屏障打开
 屏障打开
 * */

3. 信号量:Semaphore

3.1 使用场景

若有m个资源,但有n条线程(n>m),因此同一时刻只能允许m条线程访问资源,此时可以使用Semaphore控制访问该资源的线程数量。

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

3.2 代码实现

public class SemaphoreTest {
    // 创建信号量对象,并给予3个资源
    static Semaphore semaphore = new Semaphore(3);
    private static ExecutorService threadPool = Executors.newCachedThreadPool();
    public static void main(String[] args) {
        // 开启10条线程
        for ( int i=0; i<10; i++ ) {
            final int j=i;
            threadPool.execute(() -> {
                try {
                    // 获取资源,若此时资源被用光,则阻塞,直到有线程归还资源
                    semaphore.acquire();
                    // 任务代码
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println("任务执行完毕"+j+",等待的线程数"+semaphore.getQueueLength());
                    // 释放资源
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }
}
/**
 任务执行完毕1,等待的线程数7
 任务执行完毕3,等待的线程数6
 任务执行完毕5,等待的线程数5
 任务执行完毕0,等待的线程数4
 任务执行完毕2,等待的线程数3
 任务执行完毕4,等待的线程数2
 任务执行完毕7,等待的线程数1
 任务执行完毕6,等待的线程数0
 任务执行完毕8,等待的线程数0
 任务执行完毕9,等待的线程数0
 */

4、线程间交换数据的Exchanger

4.1简介

Exchanger(交换者)是一个用于线程间协作的工具类,用于进行线程间的数据交换。

4.2使用场景

可以用于遗传算法,也可以用于校对工作,查看两个线程返回的数据是否一致。

public class ExchangerTest {
    private static final Exchanger<String> exgr = new Exchanger<>();
    private static ExecutorService threadPool = Executors.newFixedThreadPool(2);

    public static void main(String[] args) {
        threadPool.execute(() -> {
            try {
                String A = "银行流水A";
                exgr.exchange(A);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        threadPool.execute(() -> {
            try {
                String B = "银行流水B";
                String A = exgr.exchange(B);
                System.out.println("A和B数据是否一致:" + A.equals(B) + ",A的录入是"+ A + ",B录入的是" + B);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        threadPool.shutdown();
    }
}
/**
 A和B数据是否一致:false,A的录入是银行流水A,B录入的是银行流水B
 * */

猜你喜欢

转载自blog.csdn.net/sinat_27143551/article/details/80280631