举例详解 java.util.concurrent 并发包 4 种常见类

Posted 满眼*星辰

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了举例详解 java.util.concurrent 并发包 4 种常见类相关的知识,希望对你有一定的参考价值。

JUC

具体就是指 :java.util.concurrent 的并发包

1. 可重入互斥锁 ReentrantLock

作用

保证线程安全,和sychronized使用差不所

常用方法

ReentrantLock():构造方法,参数为true则为公平锁,默认非公平锁
lock():加锁
unlock():释放锁

代码举例

    //全局变量
    private static int number = 0;
    //循环次数
    private static final int maxSize = 100000;

    public static void main(String[] args) throws InterruptedException {
        //1.创建手动锁
        Lock lock = new ReentrantLock();

        //+10w
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < maxSize; i++) {
                    //2.加锁
                    lock.lock();
                    try {
                        number++;
                    }finally {
                        //3.释放锁
                        lock.unlock();
                    }
                }
            }
        });
        t1.start();

        //-10w
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < maxSize; i++) {
                    lock.lock();
                    try {
                        number--;
                    }finally {
                        lock.unlock();
                    }
                }
            }
        });
        t2.start();

        //等t1,t2线程执行完
        t1.join();
        t2.join();

        System.out.println("运行结果为:" + number);
    }

最后输出0,线程安全

注意事项:
(1)lock 写在 try 之前
(2)一定要记得在 finally 里面进行 unlock()

2. 信号量 Semaphore

作用

用来控制锁的数量

常见方法

Semaphore():构造方法,可以设置信号量个数,以及是否为公平锁
acquire() :尝试获取锁,如果可以正常获取到,则执行后面的业务逻辑,如果获取失败,则阻塞等待
release() :释放锁

代码举例

我们构造4辆车,以及2个停车位,让4辆车进入2个停车位,用到Semaphore信号量

    public static void main(String[] args) {
        //创建信号量
        Semaphore semaphore = new Semaphore(2);
        //线程池执行一个任务相当于一辆车进入停车场
        ThreadPoolExecutor executor = new ThreadPoolExecutor(10,10,0,
                TimeUnit.SECONDS,new LinkedBlockingQueue<>(100));
        for (int i = 0; i < 4; i++) {
            //创建任务1
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + " 到达停车场");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //试图进入车位
                    try {
                        //尝试获取锁
                        semaphore.acquire();
                        //当代码执行到此处说明已经获取到了锁
                        System.out.println(Thread.currentThread().getName() + " 进入车位----");
                        //车辆停留的时间构建
                        int num = 1 + new Random().nextInt(5);
                        try {
                            Thread.sleep(num * 1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "离开停车场。。。。");

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        //释放锁
                        semaphore.release();
                    }
                }
            });
        }
    }

在这里插入图片描述
可以看到,始终只有2辆车占用停车位,哪个车走了,停车位让出来了,后面的车才能进入

从而就实现了流量控制

3. 计数器 CountDownLatch

作用

计数器是用来保证一组线程同时完成某个任务

常用方法

CountDownLatch():构造方法,参数为n个线程
await() :等待,当线程数量不满足 CountDownLatch 的数量的时候,执行此代码会阻塞等待,直到数量满足之后,再执行 await 之后的代码
countDown():计数器数量-1

实现原理

在 CountDownLatch 里面有一个计数器,每次调用countDown() 方法的时候,计数器数量-1,直到减到 0 之后,就可以执行 await() 之后的代码了

代码实例

设置5个人同时起跑,每到达终点一个人,计数器-1,最后计数器为0时,公布成绩

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(5);

        for (int i = 1; i < 6; i++) {
            int finalI = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "开始起跑");
                    try {
                        Thread.sleep(finalI * 1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "到达终点");
                    //计数器-1
                    latch.countDown();
                }
            }).start();
        }
        //阻塞等待
        latch.await();
        System.out.println("所有人都到达了终点,公布排名");
    }

在这里插入图片描述
由此可见,当计数器没到0之前,就会阻塞等待,到0后,则执行await后面的代码

缺点

CountDownLatch 计时器的使用是一次性的,当用完一次后,就不能再使用了

4. 循环屏障 CyclicBarrier

作用

也是计时器,不过这个可以解决CountDownLatch的缺点,可以多次进行计数

常用方法

CyclicBarrier(int,Runnable):在int线程计数器为0时,继续执行runnable方法
await():等待,(1)计数器 -1 (2)判断计数器是否为 0,如果为 0 执行之后的代码,如果不为 0 阻塞等待
PS:当计数器为 0 时,首先会执行 await 之后的代码,将计数器重置

代码举例

和上面计数器例子一样,不过这时候10个线程起跑,需要每5个人记录一下时间

    public static void main(String[] args) throws InterruptedException {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable() {
            @Override
            public void run() {
                System.out.println("执行了CyclicBarrier里面的Runnable");
            }
        });

        for (int i = 1; i < 11; i++) {
            int finalI = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "开始起跑");
                    try {
                        Thread.sleep(1000 * finalI);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //
                    try {
                        System.out.println(Thread.currentThread().getName() + "等待其他人---");
                        //计数器-1,判断计数器是否为0
                        cyclicBarrier.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                    // 代码执行到此行,说明已经有一组线程满足条件了
                    System.out.println(Thread.currentThread().getName() + "执行结束。。。");
                }
            }).start();
        }

    }

在这里插入图片描述
可以看到此类可以循环的计数,每次计数器为0时,又将计数器置为初始值
在这里插入图片描述

CyclicBarrier 和 CountDownLatch 区别

  • CountDownLatch 计数器只能使用一次;
  • CyclicBarrier 他的计数器可以重复使用;

以上是关于举例详解 java.util.concurrent 并发包 4 种常见类的主要内容,如果未能解决你的问题,请参考以下文章

java并发包java.util.concurrent详解

java.util.concurrent.atomic 包详解

java.util.concurrent BlockingQueue详解

[译]Java Concurrent Atomic Package详解

简述synchronized和java.util.concurrent.locks.Lock的异同?

ConCurrent并发包 - Lock详解(转)