《Java并发编程实战》—— 第二章 线程安全性

编写线程安全的代码,核心在于要对状态访问操作进行管理,特别是对共享的和可变的状态的访问。

对象的状态

同步机制:

  • synchronized(独占的加锁方式)
  • volatile
  • 显式锁
  • 原子变量

多个线程访问同一个变量时,有3种方式保障安全:

  • 不在线程之间共享该状态变量
  • 将状态变量设计为不可变的变量
  • 在访问状态变量时使用同步

程序状态的封装性越好,就越容易实现程序的线程安全性,并且代码的维护人员也越容易保持这种方式:

  • 良好的面向对象技术
  • 不可修改性
  • 明晰的不变性规范

线程安全性
当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。

无状态对象一定是线程安全的。

原子性

竞态条件:由于不恰当的执行时序而出现不正确的结果。
当某个计算的正确性取决于多个线程的交替执行时序时,那么就会发生竞态条件。大多数竞态条件的本质是,基于一种可能失效的观察结果来做出判断或者执行某个计算。如:
先检查后执行
读取-修改-写入

当在无状态的类中添加一个状态时,如果该状态完全由线程安全的对象来管理,那么这个类仍然是线程安全的。
在java.util.concurrent.atomic包中包含了一些原子变量类,用于实现在数值和对象引用上的原子状态转换。
在实际情况中,应尽可能使用现有的线程安全对象来管理类的状态。

当在不变性条件中涉及多个变量时,各个变量之间并不是彼此独立的,而是某个变量的值会对其他变量的值产生约束。因此,当更新某一个变量时,需要在同一个原子操作中对其他变量同时进行更新。
要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量。

内置锁(同步代码块):synchronized
锁就是方法调用所在的对象。静态方法以Class对象作为锁。
是一种互斥锁,最多只有一个线程能持有这种锁。其他线程必须等待或者阻塞。
重入意味着获取锁的操作粒度是“线程”而不是“调用”。
如果用同步来协调对某个变量的访问,那么在访问这个变量的所有位置上都需要使用同步。而且,当使用锁来协调对某个变量的访问时,在访问变量的所有位置上都要使用同一个锁。
当获取与对象关联的锁时,并不能阻止其他线程访问该对象,而是阻止其他线程获得同一个锁。
每个共享的和可变的变量都应该只由一个锁来保护,从而使维护人员知道是哪一个锁。
一种常见的加锁约定是,将所有的可变状态都封装在对象内部,并通过对象的内置锁对所有访问可变状态的代码路径进行同步,使得在该对象上不会发生并发访问。
在不变性条件中的每个变量都必须由同一个锁来保护。
synchronized方法可以确保单个操作的原子性,但如果要把多个操作合并为一个复合操作,还是需要额外的加锁机制。

扫描二维码关注公众号,回复: 4678017 查看本文章

性能:
缩小同步代码块的范围,尽量将不影响共享状态且执行时间较长的操作从同步代码块中分离出去。
此外,在获取与释放锁等操作上都需要一定的开销,因此如果将同步代码块分解得过细也不好。
当执行时间较长的计算或者可能无法快速完成的操作时(例如网络I/O或控制台I/O),一定不要持有锁。

猜你喜欢

转载自blog.csdn.net/wsjtwmy/article/details/85254451
今日推荐