• JUC包(java.util.concurrent)下的常用子类



    前言

    博主个人社区:开发与算法学习社区

    博主个人主页:Killing Vibe的博客

    欢迎大家加入,一起交流学习~~

    一、对象锁juc.locks包

    在Java中除了synchronized关键字可以实现对象锁之外,java.util.concurrent中的Lock接口也可以实现对象锁。

    在这里插入图片描述

    介绍一下这个lock锁的简要实现:

    • JDK1.0就有的,需要JVM借助操作系统提供的mutex系统原语实现

    • JDK1.5之后,Java语言自己实现的互斥锁实现,不需要借助操作系统的monitor机制

    注:使用Lock接口需要显式的进行加锁和解锁操作。

    我们可以使用Lock接口的实现子类ReentrantLock来进行加锁解锁:

    ReentrantLock 可重入互斥锁. 和 synchronized 定位类似, 都是用来实现互斥效果, 保证线程安全.

    ReentrantLock 的用法:

    1. lock(): 加锁,获取锁失败的线程进入阻塞状态,直到其他线程释放锁,再次竞争,死等。
    2. trylock(超时时间): 加锁, 获取锁失败的线程进入阻塞态,等待一段时间,时间过了若还未获取到锁恢复执行,放弃加锁,执行其他代码
    3. unlock(): 解锁

    synchronized和lock的区别:

    1. synchronized 是Java的关键字, 由 JVM 实现,需要依赖操作系统提供的线程互斥原语(mutex),而Lock标准库的类和接口,其中一个最常用的子类( ReentrantLock ,可重入锁),由Java本身实现的,不需要依赖操作系统

    2. synchronized 隐式的加锁和解锁,lock需要显示进行加锁和解锁

    3. synchronized 在获取锁失败的线程时,死等;lock可以使用trylock等待一段时间之后自动放弃加锁,线程恢复执行在这里插入图片描述

    4. synchronized 是非公平锁, ReentrantLock 默认是非公平锁. 可以通过构造方法传入一个 true 开启公平锁模式. 在这里插入图片描述

    5. synchronized不支持读写锁,Lock子类ReentrantReadWriteLock支持读写锁。在这里插入图片描述

    6. 更强大的唤醒机制. synchronized 是通过 Object 的 wait / notify 实现等待-唤醒. 每次唤醒的是一个随机等待的线程.ReentrantLock搭配 Condition 类实现等待-唤醒, 可以更精确控制唤醒某个指定的线程

    小结:

    一般场景synchronized足够用了,需要用超时等待锁,公平锁,读写锁再考虑使用juc.lock

    如何选择使用哪个锁?

    • 锁竞争不激烈的时候, 使用 synchronized, 效率更高, 自动释放更方便.
    • 锁竞争激烈的时候, 使用 ReentrantLock, 搭配 trylock 更灵活控制加锁的行为, 而不是死等.
    • 如果需要使用公平锁, 使用 ReentrantLock.

    二、原子类

    原子类内部用的是 CAS 实现,所以性能要比加锁实现 i++ 高很多。原子类有以下几个:

    • AtomicBoolean
    • AtomicInteger
    • AtomicIntegerArray
    • AtomicLong
    • AtomicReference
    • AtomicStampedReference

    以 AtomicInteger 举例,常见方法有:

    addAndGet(int delta);   i += delta;
    decrementAndGet(); --i;
    getAndDecrement(); i--;
    incrementAndGet(); ++i;
    getAndIncrement(); i++;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    三、四个常用工具类

    juc包下一共有四个常用工具类:

    1. 信号量 - Semaphore
    2. 计数器 - CountDownLatch
    3. 循环栅栏 - CyclicBarrier
    4. 两个线程之间的交换器 - Exchanger

    3.1 信号量 Semaphore

    信号量Semaphore就是一个计数器,表示当前可用资源的个数

    关于信号量Semaphore有两个核心操作:

    • P - 申请资源操作
    • V - 释放资源操作

    Semaphore 的PV加减操作都是原子性的,再多线程场景下可以直接使用

    public static void main(String[] args) {
            // 在构造参数传入可用资源的个数
            // 可用资源为6个
            Semaphore semaphore = new Semaphore(6);
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(Thread.currentThread().getName() + "准备申请资源");
                        // P操作,每次申请两个资源
                        semaphore.acquire(2);
                        System.out.println(Thread.currentThread().getName() + "获取资源成功");
                        Thread.sleep(1000);
                        System.out.println(Thread.currentThread().getName() + "释放资源");
                        // V操作,默认释放一个占有的资源
                        semaphore.release(2);
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            for (int i = 0; i < 20; i++) {
                Thread t = new Thread(runnable,String.valueOf(i + 1));
                t.start();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    3.2 CountDownLatch

    有点类似于大号的join方法

    调用await方法的线程需要等待其他线程将计数器减为0才能继续恢复执行。

    public static void main(String[] args) throws InterruptedException {
            // 等待线程需要等待的线程数,必须等这10个子线程全部执行完毕再恢复执行
            CountDownLatch latch = new CountDownLatch(10);
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(new Random().nextInt(1000));
                        System.out.println(Thread.currentThread().getName() + "到达终点");
                        // 计数器 - 1
                        latch.countDown();
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            for (int i = 0; i < 10; i++) {
                Thread t = new Thread(runnable,"运动员" + i + 1);
                t.start();
            }
            // main线程就是裁判线程,需要等待所有运动员到底终点再恢复执行
            // 直到所有线程调用countdown方法将计数器减为0继续执行
            latch.await();
            System.out.println("比赛结束~最终获胜的是鹏哥,有请冠军给大家高歌一首~");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    总结

    至于CyclicBarrier和Exchanger在本篇就不多介绍,读者可以自行查阅一下官方文档进行仔细的学习~如果有问题可以私信博主,别忘了点赞收藏+关注哦!

  • 相关阅读:
    如何使用idea连通服务器上的Redis(详细版本)
    【2d游戏开发】unity实现UI框架搭建
    06 Kafka线上集群部署方案
    jenkins报错:Pseudo-terminal will not be allocated because stdin is not a terminal
    【算法】反悔贪心
    KT148A语音芯片音频的生成和压缩以及简单修音_合成方法介绍_V3
    Java输入/输出之RandomAccessFile的功能和用法
    TTL 机制排毒,线上k8s的Job已经通过API 增加了Job的TTL 时长,且成功响应,为什么系统还是清理了Job?
    【spring】初识spring基础
    完了!这 57 道面试题(美团、BAT、携程),我咋一个都不会?
  • 原文地址:https://blog.csdn.net/qq_43575801/article/details/128057659