Java并发-线程安全

209 阅读4分钟

线程安全

定义

一个类是线程安全的是指一个类在被多个线程访问时,仍然可以保持类正确持续的运行。

一个类在多线程环境下运行,不需要考虑不同线程之间的调度和交替执行,且在类外部不需要额外的同步操作保证类的正确执行,则称此类为线程安全类。

有状态和无状态对象

有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象 ,可以保存数据。在不同方法调用间不保留任何状态。其实就是有数据成员的对象。

无状态就是一次操作,不能保存数据。无状态对象(Stateless Bean),就是没有实例变量的对象。不能保存数据,是不变类,是线程安全的。具体来说就是只有方法没有数据成员的对象,或者有数据成员但是数据成员是可读的对象。

无状态对象永远都是线程安全的。

原子性

原子性指的是多个操作是一个整体不可被分割或插入,任何一个操作出现错误都会恢复到操作前的状态。比如i++这个语句本来应该为原子操作,但是在线程不安全的环境下可能不是原子性的,因为这个自增操作包括“读-写-改”三个操作。

竞争条件

当计算的正确性依赖运行时相关的时序和多线程的交替时,会产生竞争条件;换句话说想得到正确的结果,需要依赖“幸运”的时序。“检查在运行”是一个使用潜在的过期值决定下一步操作的依据。比如下面的惰性加载就是竞争条件的例子。

public class LazyInitial {
	private static LazyInitial instance;
	public static LazyInitial getInstance() {
		if (instance == null) {
			instance = new LazyInitial();
		}
		return instance;
	}
}

复合操作

为了避免竞争条件,阻止其它线程访问正在修改的变量。必须确保其他线程在操作完成前后完成后访问或者修改一个状态。

假设有操作A和B,从操作A角度去看,其他线程在操作B时,要么B已经完成或者B还未执行,这样A和B互为原子操作。

当一个不变操作涉及多个变量时,不同变量之间不是彼此独立的:某个变量的值约束着其他变量,因此更新一个变量时,要在一个原子操作中更新其他变量。

为了保证状态的一致性,要在单一的原子操作中更新相互关联的状态变量。

内部锁

Java内部提供了用于线程安全的锁sychronized关键字。这是一个互斥锁(Mutex),被sychronized修饰的方法或者方法块每次只能被一个线程访问,其他的线程会等待,直到锁被释放,否则将永远等待下去。

锁保护状态

每一个可变变量都需要唯一一个内部锁去保护。维护者应该清楚这个锁。

对象内部锁与它的状态没有多大的关系。尽管有很多采用内部锁去保护所有的域,然而这也是非必须的。即使获得对象关联的锁也无法阻止其它线程访问此对象。如果新添加的方法忘记使用锁,依然是线程不安全的。

不可武断在每个方法上增加sychronized关键字,否则可能会导致程序过多或过少的使用此同步。而且在复合操作中这种方式也不一定是线程安全的,在“缺少即添加”的情况下仍然是不安全的,还需要额外的锁去保证线程安全,同时同步方法会导致程序性能和活跃度的问题。

活跃度和性能

有时同步整个方法会带来性能问题,依次排队等待处理被称为弱并发,我们可以通过缩小sychronized块的范围来确保线程安全,同时sychronized块不能过小。

通常简单性和性能之间是相互牵制的,不要过早的为了性能而牺牲简单性。有些耗时的操作,比如I/O或者控制台,执行这些操作期间,不要占有锁。