Java多线程与并发编程 | CyclicBarrier、ReentrantLock

475 阅读2分钟

这是我参与8月更文挑战的第24天,活动详情查看:8月更文挑战

前言

每天积累一点点,积跬步至千里。今天学习JUC包中的CyclicBarrierReentrantLock

CyclicBarrier

正如字面翻译的意思一样,CyclicBarrier循环屏障CyclicBarrier也是JUC并发包下得一个工具类,它的作用就类似于一道闸门,允许一组线程相互等待,直到所有的线程都到了闸门前(又称为公共屏障点),再一起放行。这时保证同一组的所有的线程同一时间执行。**注意:**与CountDownLatch不同的是该barrier在释放等待线程后可以重用,形象点就是这道闸打开后这一组放行完会立即关上,阻拦住第二组的线程。所以这就是为什么叫 循环(Cyclic)屏障(Barrier

image.png

代码示例

public class CyclicBarrierSample {
    private static CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for(int i = 1 ; i<=20 ; i++) {
            final int index = i;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    go();
                }
            });
        }
        executorService.shutdown();
    }
    private static void go(){
        System.out.println(Thread.currentThread().getName() + ":准备就绪" );
        try {
            cyclicBarrier.await();
            System.out.println(Thread.currentThread().getName() + ":开始运行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}
  • cyclicBarrier.await()设置屏障点,当累计5个线程都准备好后,才运行后面的代码
  • 打印结果可以看出五个一组,分四次执行完毕。

应用场景

同一时间点,多个线程同时执行

  • cpu性能评测
  • 秒杀场景
  • 抢票机器人

ReentrantLock

  • ReentrantLock重入锁,当任意的线程在获得到锁之后,再次获取该锁而不会被该锁阻塞。
  • ReentrantLock设计的目标是用来替代synchronized关键字

ReentrantLock与synchronized的区别

特征synchronized(推荐)reentrantLock
底层原理JVM实现JDK实现
性能区别低 -> 高(JDK1.5+)
锁的释放自动释放(编译器保证)手动释放(finally)
编码程度简单复杂
锁的粒度读写不区分读锁、写锁
高级功能公平锁、非公平锁唤醒、Condition分组唤醒、中断等待锁

代码示例

public class ReentrantLockSample {
    public static int users = 100;//同时模拟的并发访问用户数量
    public static int downTotal = 50000; //用户下载的真实总数
    public static int count = 0;//计数器
    private static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        //调度器,JDK1.5后提供的concurrent包对于并发的支持
        ExecutorService executorService = Executors.newCachedThreadPool();
        //信号量,用于模拟并发的人数
        final Semaphore semaphore = new Semaphore(users);
        for (int i = 0; i < downTotal; i++) {
            executorService.execute(() -> {
                //通过多线程模拟N个用户并发访问并下载
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();//关闭调度服务
        System.out.println("下载总数:" + count);
    }
    //线程不安全
    public static void add() {
        lock.lock();//上锁
        try {
            count++;
        } finally {
            lock.unlock(); //解锁,一定要放在finally里面否则会出现死锁
        }
    }
}

在现实开发中一般不推荐使用这种方式,首先每次都需要手动的去释放资源,稍有疏忽就会造成严重后果,其次在JDK1.5synchronized的性能也不差。