Java 中的并发工具类

一、等待多线程完成的CountDownLatch

CountDownLatch 允许一个或多个线程等待其他线程完成操作,类似于 join()

CountDownLatch 的构造函数接受一个int类型参数,表示计数器,调用CountDownLatch的 countDown() 方法会使计数器 减一,CountDownLatch的await()方法会阻塞线程直到计数器为0

举例

用一个所有用户(线程)都模拟加载到 100%之后才能开始游戏(主线程运行)的例子体会CountDownLatch的用法

package lockTest;

import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @Description:
 * @Author: Aiguodala
 * @CreateDate: 2021/4/22 15:52
 */

public class CountDownLatchTest {

    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        CountDownLatch latch = new CountDownLatch(10);
        Random random = new Random();
        String[] allPlayers = new String[10];
        for (int i = 0; i < 10; i++) {
            int k = i;
            threadPool.submit(() -> {
                for (int j = 0; j <= 100; j++) {
                    try {
                        Thread.sleep(random.nextInt(100));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    allPlayers[k] = j + "%";
                    System.out.print("\r" + Arrays.toString(allPlayers));
                }
                latch.countDown();
            });
        }
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println();
        System.out.println("游戏开始");
        threadPool.shutdown();
    }
}

二、同步屏障CyclicBarrier

CyclicBarrier 做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到所有线程都到达屏障,所有被屏障拦截的线程才会继续运行,可用于多线程计算数据,最后合并计算结果的场景

举例

模拟某公司记录了全功全一年的每个人对应的工资,现在需要统计公司每个月需要付出多少薪水,先用多线程处理每个人每个月的薪水,处理完成后进入屏障,再用一个线程合并计算结果

package lockTest;

import java.util.Map;
import java.util.concurrent.*;

/**
 * @Description:
 * @Author: Aiguodala
 * @CreateDate: 2021/4/22 16:56
 */

public class CyclicBarrierTest {

    public static void main(String[] args) {
        Salary salary = new Salary();
        salary.count();
    }
}

class Salary implements Runnable{


    /**
     * 创建4个屏障,处理完后执行当前类的run 方法
     */
    private CyclicBarrier c = new CyclicBarrier(4, this);

    /**
     * 模拟只启动4个线程
     */
    private Executor executor = Executors.newFixedThreadPool(4);

    /**
     * 保存每个人的工资
     */
    private ConcurrentHashMap<String, Integer> workerSalaryCount = new ConcurrentHashMap<>();


    public void count() {
        for (int i = 0; i < 4; i++) {
            executor.execute(() -> {
                // 模拟计算工资
                workerSalaryCount.put(Thread.currentThread().getName(), 1000);
                try {
                    c.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            });
        }
    }

    @Override
    public void run() {
        int res = 0;
        for (Map.Entry<String, Integer> entry : workerSalaryCount.entrySet()) {
            res += entry.getValue();
        }
        workerSalaryCount.put("res",res);
        System.out.println(res);
    }
}

CountDownLatch 和 CyclicBarrier 的区别

  • CountDownLatch 的计数器只能使用一次,但是CyclicBarrier 的计数器可以通过reset()方法重置
  • CyclicBarrier 还提供了更多实用方法,如getNumberWaiting() 可以获得阻塞线程数量,isBroken() 方法可以了解阻塞的线程是否被中断等,因此能处理更复杂的业务场景

三、控制并发线程数的Semaphore

Semaphore(信号量),是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理地使用公共资源,可以用于做流量控制,特别是公用资源有限的应用场景

举例

例如有一个需求,需要读取几万个文件的数据,因为都是IO密集型,所以可以启动相应线程并发读取,但是读入后,如果还需要存储到数据库,数据库连接数只有十个,则必须控制只有十个线程能获取数据库连接。

package lockTest;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * @Description:
 * @Author: Aiguodala
 * @CreateDate: 2021/4/22 17:16
 */

public class SemaphoreTest {
    
    /**
     * 线程数
     */
    private static final int THREAD_COUNT = 30;

    /**
     * 固定线程数的连接池
     */
    private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);

    /**
     * 信号量控制10
     */
    private static Semaphore s = new Semaphore(10);

    public static void main(String[] args) {
        for (int i = 0; i < THREAD_COUNT; i++) {
            int index = i;
            threadPool.execute(() -> {
                try {
                    s.acquire();
                    System.out.println("保存数据" + index);
                    Thread.sleep(3000);
                    s.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        threadPool.shutdown();
    }
}

四、线程间交换数据的Exchanger

Exchanger (交换者),是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换,它会提供一个同步点,在都达到同步点时,两个线程可以交换彼此数据,通过exchange()方法,如果一个线程先执行该方法,那么它会等待另一个线程也执行到该方法然后进行数据的交换

举例

例如两个线程计算数据最后进行校验,或者用于遗传算法里需要选出两个人作为交配对象,这时候会交换两人的数据,用相应逻辑得出两人的交配结果,这里用校验数据举例

package lockTest;

import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @Description:
 * @Author: Aiguodala
 * @CreateDate: 2021/4/23 13:03
 */

public class ExchangerTest {

    private static final Exchanger<String> exchager = new Exchanger<>();

    private static ExecutorService threadPool = Executors.newFixedThreadPool(2);

    public static void main(String[] args) {
        threadPool.execute(() -> {
            try {
                String A = "计算出的数据";
                exchager.exchange(A);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        threadPool.execute(() -> {
            try {
                String B = "计算出的数据";
                String A = exchager.exchange("B");
                System.out.println("A 和 B 的数据是否一致" + A.equals(B));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        });

    }
}

参考 《Java并发编程的艺术》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值