NightPxy 个人技术博客

线程安全

Posted on By NightPxy

线程安全的种类

不可变

不可变对象,在任何时候都是线程安全的.
也就说这是一种绝对线程安全.这也是一种无锁实现的一种最常用的实现

绝对线程安全

抛开不可变,要实现一个任何时候都保持绝对线程安全所需要的代价都是相当大.
事实上在JDK中线程安全集合基本都是相对线程安全.也就是需要调用方的配合

相对线程安全

这是我们通常意义说的线程安全.
比如JDK中安全集合,其里面每一个单独的步骤都是线程安全的.但在某些连续动作时,依然需要调用方同步块配合

线程兼容

也就是通常意义的线程不安全
比如JDK的非安全集合,不保证线程安全.但通过调用方的手动同步,依然可以保持线程安全的情况

线程对立

这也是一种极端情况,意思是无论调用方如何也不能保持线程安全的情况
比如System.setIn和System.setOut

线程安全的实现方法

不可变

前面已经说了,不可变是一种难得的绝对线程安全

互斥同步

互斥同步是一种线程安全的悲观实现.比如synchronized,Semaphore等等都是,通过强制对临界区串行来保持安全

非阻塞同步

互斥同步最大的问题就是线程阻塞与唤醒消耗,所以互斥同步又叫阻塞同步
与之相对的就是非阻塞同步了,非阻塞同步是一种基于冲突检测的线程安全乐观实现
比如CAS,先操作后比较,如果没有争用就成功,有争用就采取其它补救比如重试等

非阻塞同步的优势在于不需阻塞线程,但缺点在于重试之类的冲突补救措施可能很大,所以适用于竞争少或者不激烈的情况

资源隔离

线程不安全的很大一点在临界区的干扰.那么提前对临界区进行隔离也可以很好的解决线程安全
比如 ThreadLocal,对每个线程使用副本来解决线程安全. 也可以做共享数据的线程分区隔离处理等等

死锁 & 活锁

活锁
活锁是指没有被阻塞,但由于某些条件没有被满足一直重试

活锁的危害
活锁是一种务必要注意的地方.活锁空转CPU,即浪费CPU能力又实际不产生任何价值,但又不会抛出任何异常很难排查
但某些情况活锁是可能出现的.本质上,自旋锁就是一种活锁.活锁一定要注意的是必须保证在某个条件满足时能够退出,并且保证这种退出条件是可以被出现的.

死锁
出现两个或多个线程,因为争夺资源而互相等待对方释放

*死锁的四个必要条件 *

  • 互斥
    资源是独占且排它.线程本身占有的资源无法同时分享给其它线程
  • 占有并等待
    线程已占有的资源并未完全,必须等待另外部分资源
  • 无法被剥夺
    线程已占用的资源也无法被其它线程剥夺,必须等待线程自身释放
  • 循环等待
    两个线程所占有和欠缺的资源,刚好处在对方线程

破坏死锁条件而避免死锁

  • 破坏互斥
    资源是非互斥状态则不会产生死锁.比如各自持有独立副本
  • 破坏占有并等待
    因为只持有部分资源而必须等待.可以一次性分配全部所需资源而避免等待
  • 破坏无法被剥夺
    存在一个第三方的超级线程,在满足一定条件的情况下强行释放死锁线程已占用的资源
  • 破坏循环等待
    合理的调度顺序,破坏循环等待对方的情况