Java并发编程之工具类

一、并发工具类

JDK1.5 引入常用并发工具类:CountDownLatch/Semaphore/CyclicBarrier/Exchanger

1. CountDownLatch

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

  • 应用场景:常用于等待多线程运行结果

  • 原理:内部采用共享锁实现

public class CountDownLatchJob extends Thread{

    private CountDownLatch startSignal;
    private CountDownLatch doneSignal;

    CountDownLatchJob(CountDownLatch startSignal, CountDownLatch doneSignal) {
        this.startSignal = startSignal;
        this.doneSignal = doneSignal;
    }
    @Override
    public void run() {
        try {
            this.startSignal.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+": execute ");
        this.doneSignal.countDown();
    }

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch startSignal = new CountDownLatch(1);
        CountDownLatch doneSignal = new CountDownLatch(5);
        for (int i = 0; i < 5; i++) {
            new CountDownLatchJob(startSignal,doneSignal).start();
        }
        Thread.sleep(2000);
        System.out.println("thread start");
        startSignal.countDown();
        doneSignal.await();
        System.out.println("main exit");

    }
}

2. Semaphore

信号量,用于控制访问共享资源的计数器。

  • 应用场景:常用于限制可以访问共享资源的线程数

  • 原理:内部采用共享锁实现

public static void main(String[] args) {
    Semaphore semaphore = new Semaphore(2);//一次只能两个线程执行
    ExecutorService executorService = Executors.newCachedThreadPool();
    for (int i = 0; i < 10; i++) {
        executorService.submit(()->{
            try {
                semaphore.acquire();
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName()+": excute something");
                semaphore.release();

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

3. CyclicBarrier

简单来说,让一组线程到达一个屏障时阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有线程才继续运行。

  • 应用场景:多线程结果合并的操作,比如多线程计算数据,最后合并计算结果

  • 原理:底层采用ReentrantLock + Condition实现

public class CyclicBarrierJob implements Runnable {
    int [][] array;
    CyclicBarrier cyclicBarrier;
    ConcurrentHashMap val = new ConcurrentHashMap<>();
    public CyclicBarrierJob(int [][] array){
       this.array = array;
       cyclicBarrier = new CyclicBarrier(array.length,this);
    }

    public void execute(){
        for (int i = 0; i < array.length; i++) {
            int index = i;
            new Thread(()->{
                try {
                    double result = Arrays.stream(array[index]).average().getAsDouble();
                    System.out.println(Thread.currentThread().getName() + " avg:" + result);
                    val.put(index, result);
                    cyclicBarrier.await();
                    System.out.println(Thread.currentThread().getName()+" end");
                } catch (BrokenBarrierException | InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
    @Override
    public void run() {
        System.out.println(val.values().stream().mapToDouble((s) -> s).summaryStatistics().getAverage());
    }
    public static void main(String[] args) throws InterruptedException {
        int[][] array = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12, 13, 14}};
        CyclicBarrierJob cyclicBarrierJob = new CyclicBarrierJob(array);
        cyclicBarrierJob.execute();
    }
}

1.CountDownLatch 的作用是允许1或N个线程等待其他线程完成执行;而CyclicBarrier是允许N个线程互相等待

2.CountDownLatch的计算器无法被重置,CyclicBarrier可以使用reset()方法重置

3.CyclicBarrier提供高级的构造函数CyclicBarrier(int parties, Runnable barrierAction),都到达屏障优先执行barrierAction

4. Exchanger

允许两个线程之间定义同步点,进行数据交换

  • 应用场景:一个任务需要两个人做,然后互相进行校对

  • 原理:内部CAS

public class ExchangerJob extends Thread{

    static Exchanger exchanger = new Exchanger<>();
    private String content;
    public ExchangerJob(String content){
        super(content);
        this.content = "content " + content;
    }
    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName() + ":" + exchanger.exchange(content));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        new ExchangerJob("A").start();
        new ExchangerJob("B").start();
    }
}

二、 并发集合&阻塞队列

JDK1.5 之前,对于集合并发访问可以使用一下三种方式:(1)Synchronized 关键字加锁,(2)利用集合框架中的线程安全类:Vector和HashTable,(3)Collections工具类 通过调用其中的静态方法,来得到线程安全的集合,比如Collections.synchronizedList(List)

这三种方式实现原理都是采用synchronized锁,性能开销比较大

JDK1.5 开始,JDK包java.util.concurrent提供了丰富的安全并发集合类:一种是非阻塞式,另一种阻塞式;阻塞式都是队列结构

非阻塞式

  • ConcurrentHashMap

主要是通过CAS和降低锁的力度来提升并发性能(JDK1.7使用分段锁)

平时最常用的HashMap是线程不安全,并发执行put操作会引起死循环,是因为HashMap的Entry链表形成环形数据结构,Entry的next节点永不为空。

  • CopyOnWriteArrayList

Copy-On-Write原理:是先copy出一个容器(简称副本),再往新的容器里添加这个新的数据,最后把新的容器的引用地址赋值给之前旧的容器地址

类名 数据结构 说明
CopyOnWriteArrayList 基于写时复制(Copy-On-Write) 的List结构; 读不会阻塞,写会阻塞 并发读性能高,并发写空间消耗大;适合读多写少的场景
CopyOnWriteArraySet 基于写时复制(Copy-On-Write) 的Set结构; 读不会阻塞,写会阻塞 并发读性能高,并发写空间消耗大;适合读多写少的场景
ConcurrentSkipListSet 基于跳表(SkipList)实现的Set;跳表以空间换取时间, 实际直接使用ConcurrentSkipListMap实现的,类似HashSet也是使用HashMap实现的
ConcurrentSkipListMap 基于跳表(SkipList)实现的Map;跳表以空间换取时间
ConcurrentHashMap 采用分段锁技术实现
ConcurrentLinkedDeque 基于CAS实现双向无界队列(FIFO、FILO)
ConcurrentLinkedQueue 基于CAS实现单向无界队列(FIFO)

阻塞式队列

[图片上传失败...(image-8cca20-1668762493898)]

阻塞队列提供的常用方法如下:

方法/处理方式 抛出异常 返回值 一直阻塞 超时退出
插入方法 boolean add(E e) boolean offer(E e) void put(E e) boolean offer(E e, long timeout, TimeUnit unit)
移除方法 E remove() E poll() E take() E poll(long timeout, TimeUnit unit)
检查方法 E element() E peak() 不可用 不可用
类名 数据结构 说明
ArrayBlockingQueue 基于独占锁ReentrantLock+数组的阻塞单向有界队列 支持公平与非公平两种模式,公平队列:ArrayBlockingQueue fairQueue = new ArrayBlockingQueue(1000,true);
LinkedBlockingQueue 基于两个独占锁+链表的阻塞单向无界队列 其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,提升整个队列的 并发性能
PriorityBlockingQueue 基于独立锁+数组(堆)支持优先级的无界队列 内部提供自然顺序升序Comparator,也可通过构造函数参数指定自定义Comparator
DelayQueue 基于独立锁+优先级队列支持延迟获取元素的无界队列 常用于(1)缓存系统设计:一旦能从DelayQueue 中获取元素时,表示缓存有效期到了(2)定时任务调度:一旦从DelayQueue中获取到任务就开始执行,TimerQueue就是基于DelayQueue实现
SynchronousQueue 一个不存储元素的阻塞队列 每一个put 操作必须等待一个take 操作,队列本身并不存储任何元素,非常适合于传递性场景,比如在一个线程中使用的数据,传递给另外一个线程使用
LinkedTransferQueue 一个由链表结构组成的无界阻塞TransferQueue 队列 它多了tryTransfer 和transfer 方法,可以算是 LinkedBolckingQueueSynchronousQueue 和合体。transfer 方法可以把生产者传入的元素立刻transfer(传输)给消费者。如果没有消费者在等待接收元素,transfer 方法会将元素存放在队列的tail 节点,并等到该元素被消费者消费了才返回,适合线程间传递数据
LinkedBlockingDeque 基于独占锁ReentrantLock+双向链表的阻塞双向无界队列 因为是双向队列,所以 多了addFirst , addLast , offerFirst , offerLast ,peekFirst,peekLast 等方法

三、ThreadLocal

也叫本地线程变量,是为了方便每个线程处理自己的状态而引入一个机制。其思路是为每一个线程创建一个单独的变量副本,互不影响。

public static void main(String[] args) {
    for (int i = 0; i < 5; i++) {
        final int index = i;
        new Thread(()->{
            ThreadLocal startTime = new ThreadLocal<>();
            startTime.set(System.currentTimeMillis());
            long time = 500 * index;
            try {
                Thread.sleep(time);
                System.out.println(time);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("time:" + (System.currentTimeMillis() - startTime.get()));

        }).start();
    }
}
ThreadLocal.png

ThreadLocal内存泄漏根源:由于ThreadLocalMap 的生命周期跟 Thread 一样长,如果没有手动删除(remove()方法)对应 key 就会导致内存泄漏

你可能感兴趣的:(Java并发编程之工具类)