目录
JUC常⻅类
Callable 接⼝
ReentrantLock
ReentrantLock 和 synchronized 的区别
信号量Semaphore
CountDownLatch
线程安全的集合类
多线程环境使⽤ArrayList
多线程环境使⽤队列
多线程环境使⽤哈希表
2) ConcurrentHashMap
JUC常⻅类
JUC(java.util.concurrent) 的常⻅类
Callable 接⼝
Callable接口 是与Runnable接口 并行关系
相当于把线程封装了⼀个"返回值".⽅便程序借助多线程的⽅式计算结果
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(超时时间): 加锁,如果获取不到锁,等待⼀定的时间之后就放弃加锁
3.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保证从内存读取结果),只对写操作进⾏加锁
2)充分利⽤CAS特性
使用原子类对 size类型进行维护
size属性通过CAS来更新.避免出现重量级锁的情况
3)优化了扩容⽅式化整为零
发现需要扩容的线程,只需要创建⼀个新的数组,同时只搬⼏个元素过去.
分次多批的搬运减少耗时
每个来操作ConcurrentHashMap的线程,都会参与搬家的过程.每个操作负责搬运⼀⼩部分元素.
新老数组同时存在 查询时二者一起查询 添加数据仅仅存放到新数组中
当搬完最后⼀个元素再把⽼数组删掉.
over