对象的状态:存储在状态变量(例:实例/静态域)中的数据。HashMap的状态不仅存储在对象本身,还存储在许多Map.Entry对象中
要对象线程安全,需用同步机制(synchronized、volatile变量、显式锁、原子变量)来协同对对象可变状态的访问。
如果当多个线程访问同一个可变的状态变量时,没有使用合适的同步,那么程序就会出现错误.有三种方式修复这个错误:
1. 不在线程之间共享该状态变量
2. 将状态变量修改为不可变的变量
3. 在访问状态变量时,使用同步
ps:抽象和封装会降低程序性能
2.1.线程安全类:
多个线程访问时,不需额外的同步或协同,都能表现出正确的行为(行为与规范完全一致),那么就称这个类是线程安全.
无状态的类: 不包含任何域和其他类中域的引用.临时状态仅存在于局部变量中
无状态对象一定是线程安全的
2.2原子性
假定有两个操作A和B,如果从执行A的线程来看,当另一个线程执行B时,要么将B全部执行完,要么完全不执行B.
多个线程调service时,count值有偏差(实际:读取、修改、写入)
1) 竞态条件:并发中,不恰当的执行时序,出现不正确的结果.(两个人走岔路)
2) 复合操作
先检查后执行; 读取–修改–写入等操作统称为复合操作
当在无状态的类中添加一个状态时,如果该状态完全由线程安全的对象来管理,这个类仍然是线程安全的
2.3.加锁机制:
有了原子操作,为什么还要采用加锁机制?
企图通过原子引用来实现统计每一次的输入,原子引用本身都是线程安全,UnsafeCachingFactorizer 中同样存在竞态条件.无法做到同时保证两个值同时获取和修改.
更新变量时,需对其他变量同时控制(统一原子操作中)
要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量.
1) 内置锁(支持原子性)synchronized,相当于互斥锁
2) 重入
如果内置锁不可重入,下面将发生死锁
获取锁的操作粒度是线程,而不是调用
JVM将记下锁的持有者,计数器置为1,退出时,计数器递减为0时,这个锁将被释放.
2.4.用锁来保护状态
不加区别的滥用synchronized,可能会导致程序中出现过多的同步,而且还并不足以保证复合操作都是原子的.
contains和add都是原子方法,仍存在竞态条件.
2.5.活跃性与性能:
不要盲目的为了性能而牺牲简单性(可能会破坏安全性)
当执行时间较长的计算或者无法快速完成的操作时(比如: 网络IO,控制台IO),一定不要持有锁
保证安全下的性能优化: