(以下知识理解和代码来自《Java并发编程实战一书》)
1.1**线程安全性:**当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程如何交替执行,并且在主调代码中不需要额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类时线程安全的
1.2核心:对共享的和可变的状态变量访问操作进行管理。共享意味着变量可以由多个线程同时访问;可变则意味着变量的值在其生命周期内可以发生改变。
1.3Java同步机制:当多个线程同时访问可变状态变量并且至少有一个进行了写操作,则需要同步机制来协同对对象可变状态的访问。Java中主要同步机制是关键字synchronize,它提供一种独占的加锁方式,此外还包括volatile变量、显示锁和原子量。
1.4出现同步错误时,可以:
不在线程内共享该状态变量。
将状态变量修改为不可变的变量。
在访问状态变量时使用同步。
1.5当设计线程安全的类时,良好的面向对象技术、不可修改性,以及明晰的不变性规范能起到一定的帮助作用。
1.6在线程安全类中封装了必要的同步机制,因此类使用者无须进一步采用同步措施。
1.7无状态对象一定是线程安全的:
@ThreadSafe
public class StatelessFactorizer extends GenericServlet implements Servlet {
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = factor(i);
encodeIntoResponse(resp, factors);
}
}
与大多数Servlet相同,StatelessFactorizer是无状态的:不包含任何filed(属性),也不含任何对其他类中field的引用。其中i变量是局部变量,在栈中分配内存,线程有自己各自的栈,所以线程之间不会对彼此的i操作。
先展示一段代码:
@NotThreadSafe
public class UnsafeCountingFactorizer extends GenericServlet implements Servlet {
private long count = 0;
pubic long getCount() { return count}
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = factor(i);
++count;
encodeIntoResponse(resp, factors);
}
}
其中++count其实包含三个独立的操作:读取count的值,将值+1,将计算结果写入count。一个“读取”-“操作”-“写入”的操作序列,并且其结果依赖于之前的状态。所以当有多个线程同时调用service方法时,由于随机的不恰当执行序列可能出现不正确的结果是一种非常重要的情况,他有一个正式的名字:Race Condition—竞争条件。
@NotThreadSafe
public class LazyInitRace {
private ExpensiveObject instance = null;
public ExpensiveObject getInstance() {
if (instance == null)
instance = new ExpensiveObject();
return instance;
}
}
class ExpensiveObject { }
2.2.1原子操作:假设有两个操作A和B,如果从执行A的线程来看,当另一个线程执行B时,要么B全部执行完,要么完全不执行B,那么A和B对彼此来说是原子的。
2.2.2 LazyInitRace 和 UnsafeCountingFactorizer 都包含一组需要一原子方式执行的操作。
2.2.3除了采用同步机制,在实际情况下应该尽可能的使用现有的线程安全对象(例如AcomicLong)来管理类的状态。与非线程安全的对象相比,判断线程安全对象的可能状态及其状态转换情况要容易很多,更容易维护和验证该线程安全性。e.g:
@ThreadSafe
public class CountingFactorizer extends GenericServlet implements Servlet {
private final AtomicLong count = new AtomicLong(0);
public long getCount() { return count.get(); }
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = factor(i);
count.incrementAndGet();
encodeIntoResponse(resp, factors);
}
}
当有多个类的状态需要管理,并且这些状态之间有约束存在约束时,这种方法并不正确。e.g:假设我们要提高Servlet性能,当两个连续的请求对相同数值进行因数分解时,可以直接用上一次结果3果:
@NotThreadSafe
public class UnsafeCachingFactorizer extends GenericServlet implements Servlet {
private final AtomicReference<BigInteger> lastNumber
= new AtomicReference<BigInteger>();
private final AtomicReference<BigInteger[]> lastFactors
= new AtomicReference<BigInteger[]>();
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
if (i.equals(lastNumber.get()))
encodeIntoResponse(resp, lastFactors.get());
else {
BigInteger[] factors = factor(i);
lastNumber.set(i);
lastFactors.set(factors);
encodeIntoResponse(resp, factors);
}
}
上述类中对象有以下约束关系:lastNumber(缓存的因数之积)==lastFactor(缓存的数值),尽管每次调用set方法都是原子的,但是扔无法同时更新两个变量。
@ThreadSafe
public class SynchronizedFactorizer extends GenericServlet implements Servlet {
@GuardedBy("this") private BigInteger lastNumber;
@GuardedBy("this") private BigInteger[] lastFactors;
public synchronized void service(ServletRequest req,
ServletResponse resp) {
BigInteger i = extractFromRequest(req);
if (i.equals(lastNumber))
encodeIntoResponse(resp, lastFactors);
else {
BigInteger[] factors = factor(i);
lastNumber = i;
lastFactors = factors;
encodeIntoResponse(resp, factors);
}
}
此时虽然实现了线程安全,但这是一种粗暴的同步方式(只是将每个方法都作为同步方法),付出的代价很高,由于service是一个synchronize方法,每次只能有一个可以执行,这与Sevlet框架相背离了。我们可以缩小同步代码块的作用范围,同时解决这两个问题。e.g:
@ThreadSafe
public class CachedFactorizer extends GenericServlet implements Servlet {
@GuardedBy("this") private BigInteger lastNumber;
@GuardedBy("this") private BigInteger[] lastFactors;
@GuardedBy("this") private long hits;
@GuardedBy("this") private long cacheHits;
public synchronized long getHits() {
return hits;
}
public synchronized double getCacheHitRatio() {
return (double) cacheHits / (double) hits;
}
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = null;
synchronized (this) {
++hits;
if (i.equals(lastNumber)) {
++cacheHits;
factors = lastFactors.clone();
}
}
if (factors == null) {
factors = factor(i);
synchronized (this) {
lastNumber = i;
lastFactors = factors.clone();
}
}
encodeIntoResponse(resp, factors);
}
}