Java 多线程——JUC 包

JUC 包(java.util.concurrent)提供了并发编程的解决方案,CAS 是 java.util.concurrent.atomic 包的基础,AQS 是 java.util.concurrent.locks 包以及一些常用类比如 Semophore,ReentrantLock 等类的基础。

JUC 包的分类:

  • executor:线程执行器
  • locks:锁
  • atomic:原子变量类
  • tools:并发工具类
  • collections:并发集合

并发工具类

包括倒计时闭锁 CountDownLatch、栅栏 CyclicBarrier、信号量 Semaphore、交换器 Exchanger。

CountDownLatch

倒计时闭锁 CountDownLatch 也称为倒计时计数器,可以让主线程等待一组事件发生后继续执行,事件是指 CountDownLatch 里的 countDown() 方法。

public class RocketLaunch implements Runnable {

    static final CountDownLatch latch = new CountDownLatch(10);
    static final RocketLaunch rocketLaunch = new RocketLaunch();

    @Override
    public void run() {
        // 模拟检查任务
        try {
            Thread.sleep(new Random().nextInt(10) * 1000);
            System.out.println("check complete");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //计数减一
            //放在finally避免任务执行过程出现异常,导致countDown()不能被执行
            latch.countDown();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //不建议  LinkedBlockingQueue无界队列
        ExecutorService exec = Executors.newFixedThreadPool(10);
        //10个任务,10次任务提交
        for (int i = 0; i < 10; i++) {
            exec.submit(rocketLaunch);
        }
        // 等待检查
        latch.await();// latch.await()方法要求主线程等待所有10个检查任务全部准备好才一起并行执行

        // 发射火箭
        System.out.println("Fire!");
        // 关闭线程池
        exec.shutdown();
    }
CyclicBarrier

栅栏 CyclicBarrier 可以阻塞当前线程,等待其他线程,所有线程必须同时到达栅栏位置后,才能继续执行;所有线程到达栅栏处,可以触发执行另一个预先设置的线程。

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        new CyclicBarrierDemo().execute();
    }
    private void execute() {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3); // 初始化栅栏的参与者为3
        new Thread(new Task(cyclicBarrier), "thread1").start();
        new Thread(new Task(cyclicBarrier), "thread2").start();
        new Thread(new Task(cyclicBarrier), "thread3").start();
    }
    class Task implements Runnable {
        private CyclicBarrier cyclicBarrier;
        private Task(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }
        @Override
        public void run() {
            System.out.println("线程" + Thread.currentThread().getName() + "已经到达");
            try {
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
            System.out.println("线程" + Thread.currentThread().getName() + "开始处理");
        }
    }
}

运行结果:

线程thread3已经到达
线程thread1已经到达
线程thread2已经到达
线程thread2开始处理
线程thread3开始处理
线程thread1开始处理
Semaphore

信号量 Semaphore 可以控制某个资源可被同时访问的线程个数。

public class SemaphoreDemo {
    public static void main(String[] args) {
        // 线程池
        ExecutorService exec = Executors.newCachedThreadPool();
        // 只能5个线程同时访问
        final Semaphore semp = new Semaphore(5);
        for (int index = 0; index < 20; index++) {
            final int no = index;
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try {
                        // 获取许可
                        semp.acquire();
                        System.out.println("Accessing: " + no);
                        Thread.sleep((long) (Math.random() * 10000));
                        // 访问完后, 释放
                        semp.release();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            exec.execute(runnable);
        }
        // 退出线程池
        exec.shutdown();
    }
}

只有 5 个客户端可以访问,其他客户端只能等待已经获取许可的 5 个客户端调用 release() 方法释放许可,才能进行访问。

Exchanger

交换器 Exchanger 主要用于线程之间数据交换,可以实现两个线程到达同步点后,相互交换数据。先到达同步点的线程会被阻塞,当两个线程都到达同步点后,开始交换数据。

public class ExchangerDemo {
    private static Exchanger<String> exchanger = new Exchanger<String>();
    public static void main(String[] args) {
        ExecutorService exec = Executors.newFixedThreadPool(2);
        // 线程一
        exec.execute(() -> {
            try {
                String text = exchanger.exchange("hello1");
                System.out.println("thread1: " + text);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        // 线程二
        exec.execute(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
                String text = exchanger.exchange("hello2");
                System.out.println("thread2: " + text);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
}

Lock(Java5新特性)提供了比synchronized方法和synchronized代码块更广泛的锁定操作,Lock允许实现更灵活的结构,并且支持多个相关的Condition对象。

Java 中常见的锁有独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁。

ReentrantLock

特点:

  • 性能未必比 synchronized 高,并且也是可重入的。

  • ReentrantLock 能够实现比 synchronized 更细粒度的控制,如设置公平性 fairness;

  • 基于 AQS 实现,AQS 是 Java 并发用来构建锁或其他同步组件的基础框架,JUC( java.util.concurrent)package 的核心;

  • ReentrantLock 调用 lock() 之后,必须调用 unlock() 释放锁;

  • ReentrantLock 是一种排他锁,同一时间只有一个线程在执行 ReentrantLock.lock() 方法后面的任务。

  • ReentrantLock 实现等待/通知,可以借助于 Condition 对象实现

synchronized 和 ReentrantLock 的区别:

  • synchronized 是非公平锁。
  • synchronized 是关键字,ReentrantLock 是类;
  • 加锁机制不同。synchronized 底层操作的是 Java 对象头中的 Mark Word,ReentrantLock 底层调用 Unsafe 类的 park() 方法加锁

公平性是减少线程饥饿的情况发生的一个办法,线程饥饿指个别线程长期等待锁,但却始终无法获取锁的情况。

Java 默认的调度策略很少会导致饥饿的发生,若要保证公平性,则要引入额外的开销,导致一定的吞吐量下降,所以建议只有当程序确实有公平性需要的时候,才有必要去指定公平锁。

读写锁
ReentrantReadWriteLock

排他锁 ReentrantLock 虽然保证了实例变量的线程安全性,但效率却是十分低下的。所以在 JDK 中提供了一种读写锁 ReentrantReadWriteLock 类,使用它可以加快运行效率,在某些不需要操作实例变量的方法中,完全可以使用读写锁 ReentrantReadWriteLock 来提升该方法的代码运行速度。

读写锁包含两个锁,一个是读操作相关的锁,也称为共享锁;另一个是写操作相关的锁,也就做排他锁。也就是多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥。多个 Thread 可以同时进行读取操作,但是同一时刻只允许一个 Thread 进行写入操作。

猜你喜欢

转载自blog.csdn.net/lwl2014100338/article/details/107883869