仅作笔记使用,内容多摘自《java并发编程实战》
线程安全的定义
当多个线程访问某个类时,不管运行时环境采用何种调度方式或者线程将如何交替运行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么这个类就是线程安全的。(也可以简单地说:当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。)
竞态条件
在并发编程中,由于不恰当的执行时序而出现不正确的结果,这种情况称为竞态条件。 如下图:
最常见的竞态条件类型是“先检查后执行”,即通过一个可能失效的结果去判断接下来的动作。要避免竞态条件,就必须在某个线程修改变量时,通过某种方式防止其他线程使用该变量。
解决同步问题的思路
如果当多个线程访问同一个可变的状态变量时没有使用合适的同步,那么程序就会出现错误,可尝试使用下面三种方法修复问题:
- 不在线程间共享该状态变量。
- 将状态变量修改为不可变的变量。
- 在访问状态变量时使用同步。
同步
当多个线程需要访问一个状态变量,且存在线程对变量进行写操作时,必须采用同步去协调这些线程对变量的访问。java主要的同步机制是通过synchronized关键字实现的,但除此之外,在一些特定的场景下可以使用volatile类型变量、显式锁以及原子变量进行同步处理。
最低安全性
当线程在没有同步的情况下读取变量时,可能会得到一个失效值,但这个值至少是之前某个线程设置的值,而不是一个随机的值。这种安全性则称为最低安全性。最低安全性适用于大多数变量(非volatile的64位变量double、long除外,因为JVM允许将64位的读/写操作分解成两个32位的操作进行)。
重排序
重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。
volatile
当把变量声明为volatile后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者其他处理器不可见的地方,因此在读取volatile变量时总会返回最新写入的值。
发布与逸出
发布一个对象,是指使对象能够在当前作用域之外的代码中使用。而某个不应该发布的对象被发布时,称为逸出,也称逃逸。
线程封闭
当访问共享的可变数据时,通常需要使用同步。一种避免使用同步的方式是不共享数据。如果仅在单线程内访问数据,就不需要同步。这种技术被称为线程封闭。
Ad-hoc线程封闭:指维护线程封闭的职责完全由程序实现来承担。Ad-hoc线程封闭非常脆弱极易破坏,建议使用栈封闭或ThreadLocal来实现线程封闭。
栈封闭(也称作线程内部/局部使用):栈封闭是线程封闭的特例,在栈封闭中,只能通过局部变量才能访问对象。
不变性
满足同步需求的另一种方法是使用不可变对象。不可变性不等于将对象中的所有的域都声明为final类型,即便对象中的所有的域都声明为final类型,这个对象也仍旧是可变的,因为在final类型的域中可以保存对可变对象的引用。当满足下列条件时,对象才是不可变的:
- 当对象创建完成后,其状态就不可修改。
- 对象的所有域都是final类型。
- 对象是正确创建的(在对象创建期间,this引用未逸出)。
安全发布的常用模式
要安全的发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见。一个正确构造的对象可以通过以下方式来安全的发布:
- 在静态初始化函数中初始化一个对象引用。
- 将对象的引用保存到volatile类型的域或者AtomicReferance对象中。
- 将对象的引用保存到某个正确构造对象的final类型域中。
- 将对象的引用保存到一个由锁保护的域中。
java线程安全库中常用的容器
- 键值:Hashtable、 Collections.synchronizedMap 、ConcurrentMap
- 列表/集合:Vector、CopyOnWriteArrayList、CopyOnWriteArraySet、Collections.synchronizedList、Collections.synchronizedSet
- 队列:BlockingDeque、ConcurrentLinkedQueue
可重入锁、读写锁
- ReentrantLock、ReentrantReadWriteLock
java监视器模式
java监视器模式仅仅是一种编写代码的约定,对于任何一种锁对象,只要自始至终都使用该锁对象,都可以用来保护对象的状态。
遵循java监视器模式的对象会把对象的所有可变状态都封装起来,并由对象自己的内置锁来保护。(个人理解:Collections.synchronizedMap应该就是使用了该模式实现的线程安全)
非阻塞算法与无锁算法
在基于锁的算法中可能会发生各种活跃性故障。如果线程在持有锁时因为阻塞I/O、内存页缺失或者其他延迟导致推迟执行,那么很可能导致其他所有线程都无法继续之执行。如果在某种算法中,一个线程的失败或挂起不会导致其他线程也失败或挂起,那么这种算法就被称为无阻塞算法。
如果在算法的每个步骤中都存在某个线程能够执行下去,那么这种算法就被称为无锁算法。无锁算法中典型的是CAS(compare and swap)算法,当多个线程竞争同一个CAS时,总有一个线程可以胜出并执行下去。
优先级反转
举一个典型的例子:高优先级的线程需要访问被低优先级的线程上锁的共享变量,而低优先级的线程需要访问其他资源并完成任务后才可以释放锁,但这些资源被中优先级的进程占用,故低优先级的进程被阻塞,从而高优先级的资源也被阻塞,于是相当于高优先级的进程被一堆优先级比自己低的进程阻塞住导致其无法及时执行,即为优先级反转。