初识多线程plus(2.0)

目录

JUC常⻅类

[Callable 接⼝](#Callable 接⼝)

ReentrantLock

[ReentrantLock 和 synchronized 的区别](#ReentrantLock 和 synchronized 的区别)

信号量Semaphore

CountDownLatch

线程安全的集合类

多线程环境使⽤ArrayList

多线程环境使⽤队列

多线程环境使⽤哈希表

2) ConcurrentHashMap ConcurrentHashMap)


JUC常⻅类

JUC(java.util.concurrent) 的 常⻅类

Callable 接⼝

Callable接口 是与Runnable接口 并行关系

相当于把线程封装了⼀个**"返回值"**.⽅便程序借助多线程的⽅式计算结果

java 复制代码
 Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int result = 0;
                for (int i = 1; i <= 100; i++) {
                    result += i;
                }
                return result;
            }
        };

        FutureTask<Integer> futureTask = new FutureTask<>(callable);

        Thread t = new Thread(futureTask);
        t.start();

        System.out.println(futureTask.get());

thread 本身不具有获取结果的方法 所以 futureTask 对象获取结果

此外

Callable 和 Runnable相对,都是描述⼀个"任务".Callable描述的是带有返回值 的任务,Runnable 描述的是不带返回值的任务.

Callable 通常需要搭配 FutureTask 来使⽤.FutureTask⽤来保存Callable的返回结果.因为 Callable 往往是在另⼀个线程中执⾏的,执⾏完并不确定.

FutureTask 就可以负责这个等待结果出来的⼯作.

ReentrantLock

可重⼊互斥锁(与synchronized类似,都是实现互斥效果 并 保证线程安全的)

ReentrantLock 有以下三种常见的方法 :

  1. lock(): 加锁,如果获取不到锁就死等.

2**.**trylock(超时时间): 加锁,如果获取不到锁,等待⼀定的时间之后就放弃加锁

  1. unlock(): 解锁

ReentrantLock 和 synchronized 的区别

其一 JVM内外实现的差别synchronized 是⼀个关键字 是JVM 内部实现的

ReentrantLock 是标准库的⼀个类,在JVM外实现的

其二 对待阻塞的区别 synchronized 在申请锁失败时,会死等

但是 ReentrantLock 会可以使用 trylock 等待⼀定的时间之后就放弃加锁

其三 锁的类别 synchronized 是⾮公平锁,

ReentrantLock默认是⾮公平锁.可以通过构造⽅法传⼊⼀个true开启 公平锁模式.

其四 synchronized使⽤时不需要⼿动释放锁.

ReentrantLock使⽤时需要⼿动释放.使⽤起来更灵活,但 是也容易遗漏 unlock.

其Ⅴ 更强的唤醒机制 synchronized 是通过 Object 的 wait/notify 实现等待-唤醒 每次唤醒的是 ⼀ 个 随机等待 的线程

ReentrantLock搭配 Condition类 实现等待-唤醒,可以更精确控制 唤醒某个 指定的线程.

信号量Semaphore

信号量,⽤来表⽰ "可⽤资源的个数" . 本质上就是⼀个 计数器.

CountDownLatch

同时等待N个任务执⾏结束.

每个任务执⾏完毕 在 CountDownLatch 内部的计数器同时⾃增

线程安全的集合类

多线程环境使⽤ArrayList

1) 自行完成加锁 (synchronized或者ReentrantLock)

分析 代码 将其打包成 '原子' 再进行加锁

2) Collections.synchronizedList(new ArrayList);

本质作为 套壳 像是Vector, Stack, HashTable 内含有synchronized

3)使⽤CopyOnWriteArrayList

CopyOnWrite容器(写时复制的容器)

当想在容器内添加新数据时 不是选择直接加入 而是先copy 一个新的容器在其添加数据

添加完元素之后,再将原容器的引⽤指向新的容器

读多写少 的场景下, 性能很⾼,不需要加锁竞争反之会非常低效

占⽤内存较多. 新写的数据不能被第⼀时间读取到.

也是显然易见的

多线程环境使⽤队列

1)ArrayBlockingQueue

基于数组实现的阻塞队列

2)LinkedBlockingQueue

基于链表实现的阻塞队列

3)PriorityBlockingQueue

基于堆实现的带优先级的阻塞队列

4)TransferQueue

最多只包含⼀个元素的阻塞队列

多线程环境使⽤哈希表

HashMap本⾝不是线程安全的.

但是多线程中使用 哈希表仅此为二

Hashtable

只是简单的把关键⽅法加上了 synchronized 关键字.

相当于直接针对 Hashtable对象本⾝加锁.

所以多线程中 访问同⼀个 Hashtable 就会直接造成 锁冲突

此外 size属性 也是通过 synchronized 来控制同步,是⽐较慢的

如果⼀旦触发扩容,就由该线程完成整个扩容过程.

这个过程会涉及到⼤量的元素拷⻉,效率会⾮常低

但是 实际上 两个线程恰巧同时访问一个链表的概率是比较低的

ConcurrentHashMap

相⽐于 Hashtable做出了⼀系列的改进和优化

  1. 简单说 锁对象不在是单个对象

而是锁整个对象,⽽是**"锁桶"** (⽤每个链表的头结点作为锁对象),⼤⼤ 降低了锁冲突的概率

读操作没有加锁(但是使⽤了volatile保证从内存读取结果),只对写操作进⾏加锁

  1. 充分利⽤CAS特性

使用原子类对 size类型进行维护

size属性通过CAS来更新.避免出现重量级锁的情况

  1. 优化了扩容⽅式 化整为零

发现需要扩容的线程,只需要 创建⼀个新的数组,同时只搬⼏个元素过去.

分次多批的搬运减少耗时

每个来操作ConcurrentHashMap的线程,都会参与搬家的过程.每个操作负责搬运⼀⼩部分元素.

新老数组同时存在 查询时二者一起查询 添加数据仅仅存放到新数组中

当搬完最后⼀个元素再把 ⽼数组 删掉.

over

相关推荐
Rabitebla1 小时前
C++ 多态详解:从概念到虚表底层原理(代码轰炸)
开发语言·c++
布朗克1681 小时前
33 设计模式精讲
java·单例模式·设计模式
砍材农夫1 小时前
python 如何一次性安装项目所有依赖包(pip和uv)
开发语言·python·pip·uv
IpdataCloud1 小时前
信贷审核中如何验证用户地址与IP属地一致性?用IP查询工具实现反欺诈
开发语言·tcp/ip·金融·php·ip
码语智行1 小时前
基于word模板导出人员信息
java
云水-禅心1 小时前
解决MacOS 安装Python之后默认版本指向不正确问题
开发语言·python·macos
冰暮流星1 小时前
javascript之this关键字
开发语言·前端·javascript
rit84324991 小时前
基于Qt的串口上位机控制蓝牙小车程序
开发语言·qt
百度Geek说1 小时前
CodingAgent 的原始森林困境:一张地图能解决什么?
开发语言·javascript·ecmascript·coding agent