J.U.C并发工具类整理

工具类

  • CountDownLatch

CountDownLatch 的作用是让一些线程阻塞直到另外一些线程完成一系列操作后才被唤醒。

CountDownLatch 在初始时设置一个数值,当一个或者多个线程使用 await() 方法时,这些线程会被阻塞。其余线程调用 countDown() 方法,将计数器减去1,当计数器为0时,调用 await() 方法被阻塞的线程会被唤醒,继续执行。

可以理解为,等大家都走了,保安锁门。

面试题:

实现一个容器,提供两个方法,add,size 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束.

 

package interview;


import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

/**
 * @Description: 实现一个容器,提供两个方法,add,size 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束.
 * @Author: zhangkai
 * @Date: 2020/3/22 17:20
 */
public class Test01ByCountDown {

    List list = new ArrayList<>();

    public void add(int i) {
        list.add(i);
    }

    public int size() {
        return list.size();
    }

    public static void main(String[] args) {
        Test01ByCountDown test01 = new Test01ByCountDown();

        CountDownLatch countDownLatch = new CountDownLatch(1);

        new Thread(() -> {
            System.out.println("t2启动");
            if (test01.size() != 5) {
                try {
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t2结束");
            }
        }, "t2").start();

        new Thread(() -> {
            System.out.println("t1启动");
            for (int i = 0; i < 10; i++) {
                test01.add(i);
                System.out.println("添加" + i);
                if (test01.size() == 5) {
                    System.out.println("countdown is open");
                    countDownLatch.countDown();
                }
            }
        }, "t1").start();
    }
}
  • CyclicBarrier

CyclicBarrier 是指可以循环使用的屏障,让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障,屏障才会开门,被屏障拦截的线程才会继续工作,线程进入屏障通过 await() 方法。

可以理解为,大家都到齐了,才能开会。

  • Semaphore

信号量用于:

  • 多个共享资源的互斥使用
  • 并发线程数的控制
面试题:semaphore初始化有2个令牌,一个线程调用1次release方法,然后一次性获取3个令牌,会获取到吗?

答案:能,原因是release会添加令牌,并不会以初始化的大小为准。Semaphore中release方法的调用并没有限制要在acquire后调用。

测试:

package interview;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * @Description: semaphore初始化有2个令牌,一个线程调用1次release方法,然后一次性获取3个令牌,会获取到吗?
 *
 * availablePermits:2,semaphore.tryAcquire(3,1, TimeUnit.SECONDS):false
 * availablePermits:3,semaphore.tryAcquire(3,1, TimeUnit.SECONDS):true
 *
 * @Author: zhangkai
 * @Date: 2020/3/22 17:54
 */
public class TestSemaphore2 {

    public static void main(String[] args) {

        int permitsNum = 2;
        final Semaphore semaphore = new Semaphore(permitsNum);
        try {
            System.out.println("availablePermits:" + semaphore.availablePermits() + ",semaphore.tryAcquire(3,1, TimeUnit.SECONDS):" + semaphore.tryAcquire(3, 1, TimeUnit.SECONDS));
            semaphore.release();
            System.out.println("availablePermits:" + semaphore.availablePermits() + ",semaphore.tryAcquire(3,1, TimeUnit.SECONDS):" + semaphore.tryAcquire(3, 1, TimeUnit.SECONDS));
        } catch (Exception e) {

        }

    }
}

 结果:

availablePermits:2,semaphore.tryAcquire(3,1, TimeUnit.SECONDS):false
availablePermits:3,semaphore.tryAcquire(3,1, TimeUnit.SECONDS):true
  • Exchanger

参考:https://www.pdai.tech/md/java/thread/java-thread-x-juc-overview.html

Exchanger是用于线程协作的工具类, 主要用于两个线程之间的数据交换。它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据。这两个线程通过exchange()方法交换数据,当一个线程先执行exchange()方法后,它会一直等待第二个线程也执行exchange()方法,当这两个线程到达同步点时,这两个线程就可以交换数据了。

  • Phaser

Phaser是JDK 7新增的一个同步辅助类,它可以实现CyclicBarrier和CountDownLatch类似的功能,而且它支持对任务的动态调整,并支持分层结构来达到更高的吞吐量。

并发集合类

  • ConcurrentModificationException

这个异常也就是并发修改异常,java.util.ConcurrentModificationException。导致这个异常的原因,是集合类本身是线程不安全的。

解决方案

  • 使用 Vector, Hashtable 等同步容器
  • 使用 Collections.synchronizedxxx(new XX) 创建线程安全的容器
  • 使用 CopyOnWriteList, CopyOnWriteArraySet, ConcurrentHashMap 等 j.u.c 包下的并发容器。
  • CopyOnWriteArrayList

/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //旧数组
            Object[] elements = getArray();
            int len = elements.length;
            //新数组
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            //元素添加到新数组
            newElements[len] = e;
            //替换为新数组
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

添加元素时,不是直接添加到当前容器数组,而是复制到新的容器数组,向新的数组中添加元素,添加完之后将原容器引用指向新的容器。

这样做的好处是可以对该容器进行并发的读,而不需要加锁,因为读时容器不会添加任何元素。

CopyOnWriteArraySet 本身就是使用 CopyOnWriteArrayList 来实现的。

 

你可能感兴趣的:(底层原理/心得,高并发/高性能/高可用)