java 并发编程 不是什么高深技术,程序设计时,将并发问题考虑进去,可以增强程序的健壮性,还能避免出错。
If multiple threads access the same mutable state variable without appro-priate synchronization, your program is broken. There are three ways to fix it:
----摘自 《 java Concurrency in Practice》
如果当多个选访问同一个可变的状态变量时,没有使用合适的同步,那么程序就会崩,有三种方法修复这个问题:
- 不在线程之间共享状态变量。
- 将状态变量修改为不可变的变量。
- 在访问状态变量时使用同步。
线程安全性:
public class StatelessFactorizer implements Servlet {
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = factor(i);
encodeIntoResponse(resp, factors);
}
}
StatelessFactorizer 是一个无状态的,都是方法级的变量,不存在并发安全问题(方法内部是线程安全的)。并发访问时,不会影响其他的StatelessFactorizer类,所以对象时线程安全的,不需要同步等线程安全性修改。
在书中提到 :
Stateless objects are always thread-safe.
原子性(Atomicity ):
当我们添加一个状态时,就要考虑线程安全问题了。看下面例子。
public class UnsafeCountingFactorizer implements Servlet {
private long count = 0;
public long getCount() {
return count;
}
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = factor(i);
++count;
encodeIntoResponse(resp, factors);
}
}
Servlet 是单例的,当多个浏览器并发访问 service方法时,由于没有使用同步,导致 UnsafeCountingFactorizer 中的count 不能正确的自增。(++count不是线程安全的,他包含了read--->assign --->write,三个操作,在此期间可能被打断,文章最后附上线程内存读写过程。)
这种由于不恰当的执行时序而出现不正确的结果是一种非常重要的情况,它有一个正式的名字:竞态条件(Race Condition)
竞态条件示例:
public class LazyInitRace {
private ExpensiveObject instance = null;
public ExpensiveObject getInstance() {
if (instance == null)①
instance = new ExpensiveObject();
return instance;
}
}
一个
非线程安全的懒加载单利类,当多个线程同时访问getInstance方法时,可能造成实例出多个不同对象,当一个线程执行到
①处,另外一个线程同时来到①并往下执行,会得到不同的对象,不能保证单利。这种“先检查,后执行”的操作,导致线程安全问题。
重入(Reentrancy ):
当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会阻塞。然而,由于内置锁是可以重入的,因此如果某个线程试图获得一个已经由他自己持有的锁,那么这个请求就会执行成功。“重入”意味着获取锁的操作是“线程的”,而不是“调用”。
public class Widget {
public synchronized void doSomething() {
}
}
public class LoggingWidget extends Widget {
public synchronized void doSomething() {
System.out.println(toString() + ": calling doSomething");
super.doSomething();
}
}