多线程进阶相关知识点
- 一.CAS
- 1.1 CAS的原子类
- 1.2 实现自旋锁
- 1.3CAS中的ABA问题
- 1.4 ABA问题的解决
- 二. callable接口
- 三.reentrantLock
- 3.1 reentrantLock与synchronized区别
- 四.信息量 semaphore
- 五. CountDownLatch
- 六. concurrentHashMap
- 6.1 concurrentHashMap的优点
一.CAS
CAS compare and swap 比较并交换 比较交换的是内存和寄存器中的值
就能原子的完成一些复杂操作,达成无锁化编程.
1.1 CAS的原子类
1.比较相等
2.如果相等 就将新值赋值给address中
3.返回操作
但上述伪代码并不是原子的
这些代码是在java.util.concurrent.atomic包中的原子类,是基于CAS实现的.
1.2 实现自旋锁
实现自旋锁,如果this.owner不为空 就一直进行"忙等" 循环会一直执行下去
此处自旋的等 虽然没有放弃cpu 但是不会参与调度, 缺点会消耗很多CPU资源
1.3CAS中的ABA问题
CAS的判定本质上是判断是否有其他的线程从中间穿插进来.
我们先看一个例子
极端情况:
在t1执行之前 t3线程又给我账户充值了500块钱,我们就分不清楚到底是取了500块钱,还是取了又充值回去的,就会引起一个BUG.
1.4 ABA问题的解决
1.约定数据变化只能是单向的(要增就只能都增)
2.如果必须是双向变化的数据,我们可以引入版本号.版本号的数字就只能是增加或者减少的.
二. callable接口
我们前面学过实现runnable接口来完成线程问题,这里的callable接口有什么不同呢?
- runnable接口关注的是过程,而不是结果,所以提供的run方法,返回值为void
- callable接口关注的是结果,提供的call方法的返回值就是callable接口的泛型类
除此之外,在callable接口中,我们为什么不能直接将callable放到Thread的参数里面呢?因为Thread没有提供相应的构造方法,我们需要借助FutureTask来辅助实现
三.reentrantLock
它是一个可重入锁, 与synchronized类似.
reentrantLock提供了两个方法, lock方法和unlock方法,但是如果unlock有时会无法解锁,这是因为碰到return或者异常之后就会执行不到unlock,所以必须将unlock方法与try-finally方法连用.
3.1 reentrantLock与synchronized区别
区别1 : reentrantLock方法提供了tryLock方法
普通的lock方法进行加锁,如果无法加锁,就会阻塞
tryLock方法加锁不成,不会阻塞,会直接返回false
区别2 : reentrantLock提供了公平锁的实现
所谓公平锁就是要遵循先来后到的原则,通过队列记录加锁线程的先后顺序.
区别3: 搭配的等待通知不一样
synchronized搭配的是 wait-notify方法
reentrantLock搭配的是Condition方法
四.信息量 semaphore
信息量表示 可用资源的个数
申请一个资源,可用资源的个数就会减1 称为P操作
释放一个资源,可用资源的个数就会加1 称为V操作
acquire表示申请一个资源 release表示释放一个资源
五. CountDownLatch
它的目的就是 多线程执行一个任务, 把大的任务拆分成几个部分 ,分给每个线程执行.
六. concurrentHashMap
我们知道哈希表中有 HashMap , Hashtable 在多线程中,我们知道.HashMap是线程不安全的所以不予考虑. Hashtable在关键方法上都有synchronized加锁,但是我们也不常用, 这就引入了concurrentHashMap
6.1 concurrentHashMap的优点
- 缩小了锁的粒度
在Hashtable中 如果修改两个不同链表中的元素时,不会有线程安全问题.如果是修改同一个链表上的元素时,就会发生锁冲突,有线程安全问题.
在concurrentHashMap中,给每一个链表都发了一把锁,因此就不会发生锁冲突.线程会更加的安全.
优点2 :充分的使用了CAS的原子操作,减少了加锁.
优点3 : 针对扩容问题,增加了优化
我们知道,在哈希表中有负载因子:描述了桶上平均有多少元素.
哈希表的查找效率是O(1) 但是你超过了原有元素的个数之后,我们要进行扩容,把旧的元素全部添加到新的元素上,如果本身有很多元素的话,会非常消耗时间.
HashMap的做法是一次性的全部扩容,就会非常耗费时间
concurrentHashMap的做法是 一次扩容一部分 分多次来扩容 ,避免了一次性的耗费时间太多的问题.避免出现某一时间段卡的情况.