JUC(Java.util.concurrent)的常见类

目录

♫ReentrantLock

♪什么是ReentrantLock

♪ReentrantLock的用法

♪ReentrantLock和synchronized的区别

♫Semaphore

♪什么是Semaphore

♪semaphore的用法

♫CountDownLatch

♪什么是CountDownLatch

♪CountDownLatch的使用

♫多线程环境使用ArrayList

♫多线程环境使用队列

♫多线程环境使用哈希表


JUC除了提供前面介绍过的线程池、定时器,还提供了多线程编程中其它一些常用的工具类和接口(ReentrantLock、Semaphore、CountDownLatch等)

♫ReentrantLock

♪什么是ReentrantLock

ReentrantLock 是 Java 标准库提供的一种可重入互斥锁 synchronized 定位类似,都是用来实现互斥效果,保证线程安全的。

♪ReentrantLock的用法

使用 ReentrantLock 需要使用  lock()  加锁, 如果获取不到锁就死等)或  trylock(超时时间)加锁, 如果获取不到锁, 等待一定的时间之后就放弃加锁)和  unlock()解锁)对代码块进行加锁解锁:
.
ReentrantLock reentrantLock = new ReentrantLock();
reentrantLock.lock();
//加锁内容
reentrantLock.unlock();

为了避免加锁内容提前退出(有return语句或异常),导致没有执行到 unlock() 语句,一般搭配 try-finally 语句执行加锁加锁操作:

ReentrantLock reentrantLock = new ReentrantLock();
try {
    reentrantLock.lock();
    //加锁内容
} finally {
    reentrantLock.unlock();
}

♪ReentrantLock和synchronized的区别

. synchronized 是一个关键字, JVM 内部实现的 ( 大概率是基于 C++ 实现 );ReentrantLock 是标准库的一个类, JVM 外实现的 ( 基于 Java 实现 )。
. synchronized 使用时不需要手动释放锁; ReentrantLock 使用时需要手动释放。
. synchronized 在申请锁失败时, 会死等; ReentrantLock 可以通过 trylock 的方式等待一段时间就放弃。
. synchronized 是非公平锁; ReentrantLock 默认是非公平锁,也 可以通过构造方法传入一个 true 开启公平锁模式。
. synchronized 是通过 Object wait / notify 实现等待 - 唤醒, 每次唤醒的是一 个随机等待的线程;  ReentrantLock 搭配 Condition 类实现等待 - 唤醒, 可以更精确控制唤醒某个指 定的线程。

♫Semaphore

♪什么是Semaphore

Semaphore 是信号量的意思,用来表示 "可用资源的个数",其本质上就是一个计数器。

例如:图书馆有500个空位,学生扫码入座,可用座位-1(相当于信号量的P操作),学生扫码离座,可用座位+1(相当于信号量的V操作),如果座位为0,就无法再扫码入座了(阻塞等待)。

注:Semaphore 的 PV 操作中的加减计数器操作都是原子的,可以在多线程环境下直接使用

♪semaphore的用法

semaphore 通过传递的参数确定总资源个数,通过 acquire() 方法申请资源(P操作),通过release() 方法释放资源(V操作):

import java.util.concurrent.Semaphore;

public class Test {
    public static void main(String[] args) {
        //初始值为3的信号量
        Semaphore semaphore = new Semaphore(3);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    //申请资源
                    semaphore.acquire();
                    Thread.sleep(100);
                    //释放资源
                    semaphore.release();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        };
        //10个线程都尝试获取资源
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(runnable);
            thread.start();
        }
    }
}

♫CountDownLatch

♪什么是CountDownLatch

CountDownLatch 用于同时等待 N 个任务执行结束, 好像跑步比赛,10个选手依次就位,哨声响才同时出发;所有选手都通过终点,才能公布成绩。

♪CountDownLatch的使用

CountDownLatch   通过传递参数确认需要完成的 任务个数,每个任务执行完毕, 都调用 countDown() 方法,在 CountDownLatch 内部的计数器同时自减,主线程中使用 await(),只有当计数器减为0时调用 countDown() 方法才会唤醒await():
import java.util.concurrent.CountDownLatch;

public class Test {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(10);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(100);
                    countDownLatch.countDown();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        };
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(runnable);
            thread.start();
        }
        countDownLatch.wait();
    }
}

♫多线程环境使用ArrayList

在多线程环境下,直接使用ArrayList往往会出现线程安全问题,我们可以通过一下几点方案解决该问题:

♩.自己使用同步机制(synchronized 或者 ReentrantLock)进行加锁

♩.使用Collections.synchronizedList(new ArrayList) synchronizedList 是标准库提供的一个基于 synchronized 进行线程同步的 List,synchronizedList 的关键操作上都带有 synchronized。
.使用 CopyOnWriteArrayList
CopyOnWrite容器即写时拷贝的容器,当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行拷贝, 复制出一个新的容器,然后在新的容器里添加元素,
添加完元素之后,再将原容器的引用指向新的容器。 这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

♫多线程环境使用队列

.ArrayBlockingQueue :基于数组实现的阻塞队列
.LinkedBlockingQueue :基于链表实现的阻塞队列
.PriorityBlockingQueue :基于堆实现的带优先级的阻塞队列
.TransferQueue :最多只包含一个元素的阻塞队列

♫多线程环境使用哈希表

 HashMap 本身不是线程安全的,在多线程环境下使用哈希表可以使用Hashtable和ConcurrentHashMap。

♩.Hashtable:只是简单的把关键方法加上了 synchronized 关键字,一个Hashtable只有一把锁,两个线程访问Hashtable中的任何数据都会出现锁竞争。

.ConcurrentHashMap

ConcurrentHashMap 相比于 Hashtable 做出了一系列的改进和优化,大大缩小了锁冲突的概率(把一大锁转换成好几把小锁)。
①. ConcurrentHashMap的做法是对每个链表分别进行加锁,这样子操作不同链表里的元素就不会发生锁冲突, 大大降 低了锁冲突的概率。
②. ConcurrentHashMap对读操作不加锁,而是 使用了 volatile 保证从内存读取结果,避免了读和写之间读到写了一半的数据。
③. ConcurrentHashMap充分利用 CAS 特性( 比如 size 属性通过 CAS 来更新), 避免出现重量级锁的情况。
④. ConcurrentHashMap优化了扩容方式,即化整为零:发现需要扩容的线程,只需要创建一个新的数组, 同时只搬几个元素过去,扩容期间, 新老数组同时存在,后续每个来操作 ConcurrentHashMap 的线程, 都会参与搬家的过程, 每个操作负责搬运一小部分元素,搬完最后一个元素再把老数组删掉。这个期间, 插入只往新数组加, 查找需要同时查新数组和老数组。
  • 18
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

记得开心一点啊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值