线程安全的种类
不可变
不可变对象,在任何时候都是线程安全的.
也就说这是一种绝对线程安全.这也是一种无锁实现的一种最常用的实现
绝对线程安全
抛开不可变,要实现一个任何时候都保持绝对线程安全所需要的代价都是相当大.
事实上在JDK中线程安全集合基本都是相对线程安全.也就是需要调用方的配合
相对线程安全
这是我们通常意义说的线程安全.
比如JDK中安全集合,其里面每一个单独的步骤都是线程安全的.但在某些连续动作时,依然需要调用方同步块配合
线程兼容
也就是通常意义的线程不安全
比如JDK的非安全集合,不保证线程安全.但通过调用方的手动同步,依然可以保持线程安全的情况
线程对立
这也是一种极端情况,意思是无论调用方如何也不能保持线程安全的情况
比如System.setIn和System.setOut
线程安全的实现方法
不可变
前面已经说了,不可变是一种难得的绝对线程安全
互斥同步
互斥同步是一种线程安全的悲观实现.比如synchronized,Semaphore等等都是,通过强制对临界区串行来保持安全
非阻塞同步
互斥同步最大的问题就是线程阻塞与唤醒消耗,所以互斥同步又叫阻塞同步
与之相对的就是非阻塞同步了,非阻塞同步是一种基于冲突检测的线程安全乐观实现
比如CAS,先操作后比较,如果没有争用就成功,有争用就采取其它补救比如重试等
非阻塞同步的优势在于不需阻塞线程,但缺点在于重试之类的冲突补救措施可能很大,所以适用于竞争少或者不激烈的情况
资源隔离
线程不安全的很大一点在临界区的干扰.那么提前对临界区进行隔离也可以很好的解决线程安全
比如 ThreadLocal,对每个线程使用副本来解决线程安全. 也可以做共享数据的线程分区隔离处理等等
死锁 & 活锁
活锁
活锁是指没有被阻塞,但由于某些条件没有被满足一直重试
活锁的危害
活锁是一种务必要注意的地方.活锁空转CPU,即浪费CPU能力又实际不产生任何价值,但又不会抛出任何异常很难排查
但某些情况活锁是可能出现的.本质上,自旋锁就是一种活锁.活锁一定要注意的是必须保证在某个条件满足时能够退出,并且保证这种退出条件是可以被出现的.
死锁
出现两个或多个线程,因为争夺资源而互相等待对方释放
*死锁的四个必要条件 *
- 互斥
资源是独占且排它.线程本身占有的资源无法同时分享给其它线程 - 占有并等待
线程已占有的资源并未完全,必须等待另外部分资源 - 无法被剥夺
线程已占用的资源也无法被其它线程剥夺,必须等待线程自身释放 - 循环等待
两个线程所占有和欠缺的资源,刚好处在对方线程
破坏死锁条件而避免死锁
- 破坏互斥
资源是非互斥状态则不会产生死锁.比如各自持有独立副本 - 破坏占有并等待
因为只持有部分资源而必须等待.可以一次性分配全部所需资源而避免等待 - 破坏无法被剥夺
存在一个第三方的超级线程,在满足一定条件的情况下强行释放死锁线程已占用的资源 - 破坏循环等待
合理的调度顺序,破坏循环等待对方的情况