java并发编程实战第二章笔记

第二章 线程安全性

2.1 什么是线程安全性

一个对象是否是需要是线性安全的,取决于它是否需要被多个线程访问 。

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

无状态对象:既不包含任何域,也不包含任何对其他类中域的引用。计算过程中的临时状态仅存在于线程栈上的局部变量中,并且只能由正在执行的线程访问。访问无状态对象的线程不会影响另一个访问同一个对象的线程的计算结果,因为这两者之间没有共享关系。因此,无状态对象是线程安全的。

2.2 原子性

原子操作:对于访问同一状态的所有操作,这个操作是一个以原子方式执行的操作。

例如i++。因为i++实际上分为三步,读取i,将值加1,写回i。

竞态条件:当某个计算的正确性取决于多个线程的交替执行时序时,那么就会发生竞态条件。最常见的就是先检查后执行操作。

/**
 * 延迟初始化中的竞态条件
 * @author cream
 *
 */
@NotThreadSafe
public class LazyInitRace{
	private Object instance = null;
	
	public Object getInstance(){
		if(instance==null){
			instance = new Object();
		}
		return instance;
	}
}

这里会存在竞态条件(先检查后执行)。假设线程B1和B2同时执行getInstance,B1看到instance为空,因此他会进入到new Object()的操作创建Object的实例。B2同时也要判断instance是否为空,此时的instance是否为空,却取决于不可预测的时序(包括线程的调度方式),以及B1要花多少时间来new一个Object的实例。如果B1在new操作时,轮到B2线程被调度,那么此时B2判断的instance也为空,因此到最后,会出现两个Object的实例。

concurrent.atomic包实现原子操作

- 基本类:AtomicInteger, AtomicLong,AtomicBoolean 
- 引用类型:AtomicReference 
- 数组类型:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
例如要实现整数的i++的原子性操作,只需调用AtomicInteger的incrementAndGet方法即可

2.3 加锁机制

要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量

Java提供了一种内置的锁机制来支持原子性:同步代码块(Synchronized),同步代码块包括两部分:一个作为锁的对象引用,一个作为由这个锁保护的代码块。

例如前面竞态条件的例子:

/**
 * 延迟初始化中的竞态条件,使用锁
 * @author cream
 *
 */
@ThreadSafe
public class LazyInitRace{
	private static Object instance = null;
	
	public static synchronized Object getInstance(){
		if(instance==null){
			instance = new Object();
		}
		return instance;
	}
}

常见的加锁约定:将所有的可变状态都封装在对象内部,并通过对象的内置锁对所有访问可变状态的代码路径进行同步。

内置锁的特性:

(1)自动获得和释放: 
每个java对象都可以隐式地扮演一个用于同步的锁的角色,这些内置的锁被称为内部锁或监视器锁,执行线程进入synchronized块之前自动获得锁,而无论是正常退出还是抛出异常,线程都会自动释放锁。因此获得内部锁的唯一途径是进入这个内部锁保护的同步块或方法。

(2)互斥性: 
内部锁在java中扮演了互斥锁的角色,即至多只有一个线程可以拥有锁,没有获取到锁的线程只能等待或阻塞直到锁被释放,因此同步块可以线程安全地原子执行。

(3)可重入性: 
可重入是指对于同一个线程,它可以重新获得已有它占用的锁。 
可重入性意味着锁的请求是基于”每线程”而不是基于”每调用”,它是通过为锁关联一个请求计数器和一个占有它的线程来实现。 



猜你喜欢

转载自blog.csdn.net/llcream/article/details/79785218
今日推荐