JDK并发包

在JDK中,已经给我们开发人员提供了并发包。如:java.util.concurrent。为了更好在项目中使用并发包下的相关类,这里对常用的类进行总结和阐述。一来对于我来说可以更好的巩固以前的知识点,二来契合知识就要分享的初衷。

先看该包的结构图:

JDK并发包_第1张图片

其实主要包括两类:同步控制工具和并发集合。

同步控制工具类

ReentrantLock 可重入锁

简单的说,可重入锁就是能够在单线程内重复获取锁,释放锁的时候需要依次释放。相对于Synchrinized比较,前者具有很好的灵活性和更细的控制粒度。ReentrantLock具备有如下的性质:

  1. 可重入: 单线程可以重复进入,但要重复退出。
  2. 提供有中断获取锁:lock.lockInterruptibly();
  3. 可限时: 超时不能获得锁,就返回false,不会永久等待构成死锁,如:lock.tryLock(12, TimeUnit.SECONDS)
  4. 公平锁: 先来先得, public ReentrantLock(boolean fair), 默认锁不公平的, 根据线程优先级竞争.

示例:

public class ReenterLock implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    public static int i = 0;

    @Override
    public void run() {
        for (int j = 0; j < 10000; j++) {
            lock.lock();
             // 超时设置
//            lock.tryLock(5, TimeUnit.SECONDS);
            try {
                i++;
            } finally {
                // 需要放在finally里释放, 如果上面lock了两次, 这边也要unlock两次
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ReenterLock tl = new ReenterLock();
        Thread t1 = new Thread(tl);
        Thread t2 = new Thread(tl);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
}

Semaphore信号量

锁一般都是互斥排他的, 而信号量可以认为是一个共享锁,

允许N个线程同时进入临界区, 但是超出许可范围的只能等待.

如果N = 1, 则类似于lock.

具体API如下, 通过acquire获取信号量, 通过release释放,代码实例如下:

public class DeadLock implements Runnable{

        // 设置5个许可
        final Semaphore semp = new Semaphore(5);

        @Override
        public void run() {
            try {
                // 获取许可
                semp.acquire();
                // 模拟线程耗时操作
                Thread.sleep(2000L);
                System.out.println("Job done! " + Thread.currentThread().getId());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 释放许可
                semp.release();
            }
        }

        public static void main(String[] args){
            // 创建20个线程的线程池
            ExecutorService service = Executors.newFixedThreadPool(20);
            final DeadLock demo = new DeadLock();
            for (int i = 0; i < 20; i++) {
                // 提交线程
                service.submit(demo);
            }
        }
}

ReadWriteLock

读写分离锁, 可以大幅提升系统并行度.

读-读不互斥:读读之间不阻塞。

读-写互斥:读阻塞写,写也会阻塞读。

写-写互斥:写写阻塞。

示例

用方法与ReentrantLock类似, 只是读写锁分离.

private static ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock();
private static Lock readLock = readWriteLock.readLock();
private static Lock writeLock = readWriteLock.writeLock();

CountDownLatch倒数计时器

一种典型的场景就是火箭发射。在火箭发射前,为了保证万无一失,往往还要进行各项设备、仪器的检查。

只有等所有检查完毕后,引擎才能点火。这种场景就非常适合使用CountDownLatch。它可以使得点火线程, 

等待所有检查线程全部完工后,再执行.

public class CountDownLatchDemo implements Runnable{
    static final CountDownLatch end = new CountDownLatch(10);
    static final CountDownLatchDemo demo = new CountDownLatchDemo();

    @Override
    public void run() {
        try {
            Thread.sleep(new Random().nextInt(10) * 1000);
            System.out.println("check complete!");
            end.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            service.submit(demo);
        }
        // 等待检查
        end.await();
        // 所有线程检查完毕, 发射火箭.
        System.out.println("fire");
        service.shutdown();
    }
}

并发容器

Collections.synchronizedMap

其本质是在读写map操作上都加了锁, 因此不推荐在高并发场景使用.

ConcurrentHashMap

关于ConcurrentHashMap的内容已经在我的上篇文章做出了详细的说明,在这里不再赘述。

BlockingQueue

阻塞队列, 主要用于多线程之间共享数据.

当一个线程读取数据时, 如果队列是空的, 则当前线程会进入等待状态.

如果队列满了, 当一个线程尝试写入数据时, 同样会进入等待状态.

适用于生产消费者模型.

源码如下:

public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            // 队列满了, 写进入等待
            while (count == items.length)
                notFull.await();
            insert(e);
        } finally {
            lock.unlock();
        }
    }

    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            // 队列空的, 读进入等待
            while (count == 0)
                notEmpty.await();
            return extract();
        } finally {
            lock.unlock();
        }
    }

注意:因为BlockingQueue在put take等操作有锁, 因此非高性能容器,

如果需要高并发支持的队列, 则可以使用ConcurrentLinkedQueue. 他内部也是运用了大量无锁操作.

CopyOnWriteArrayList

CopyOnWriteArrayList通过在新增元素时, 复制一份新的数组出来, 并在其中写入数据, 之后将原数组引用指向到新数组.

其Add操作是在内部通过ReentrantLock进行锁保护, 防止多线程场景复制多份数组.

而Read操作内部无锁, 直接返回数组引用, 并发下效率高, 因此适用于读多写少的场景.

其中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();
        }
    }

其中get方法的源码如下:

public E get(int index) {
        return get(getArray(), index);
}

可见:CopyOnWriteArrayList适合在读多写少的业务场景。

你可能感兴趣的:(JDK并发包)