定义:当多个线程访问某个类时,始终能表现出正确的行为,则称该类为线程安全的。
无状态的对象一定是线程安全的,无状态:对象不包含任何域,不含对其他类域的引用,且其局部变量只能由正在执行的线程访问,一个无状态的Servlet:
public class StatelessFactorier implements Servlets {
public void service(ServeletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = factor(i);
encodeIntoResponse(resp, factors);
}
}
竞态条件:由于不恰当的执行顺序而出现不正确的结果,如未同步的i++
“先检查后执行”中的竞态条件,if(instance == null) instance = new Object();
“复合操作”中的竞态条件:i++,包含读取、修改和写入三个操作
对每个包含多个变量的不变性条件,其所涉及的所有变量都需要由同一个锁保护
兼顾性能与并发性的因式分解的Servlet:
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);
}
}
1.编译器、处理器或运行时等会对程序的执行顺序进行重排序,缺乏同步时,多线程情况下对变量的读写操作会产生错误。
2、加锁可以保证互斥和内存可见性,将上一个持有所的线程操作的结果对下一个线程可见。
3、非volatile类型的long和double变量,jvm允许将64位的读写操作分解为两个32位操作。
4、volatile变量可以保证可见性,变量声明为volatile类型后,编译器不会将该变量上的操作与其他内存操作进行重排序,也不会缓存在寄存器或对其他处理器不可见的地方,读取时都会从主存读到工作内存,保证可见性。
在对象构造完成前发布,破坏安全性。
发布不应该发布的对象则称为逸出,调用者可以通过getStates()方法获取到私有的states变量,如:
class UnsafeStates {
private String[] states = new String[]{
"AK", "AL" /*...*/
};
public String[] getStates() {
return states;
}
}
构造方法中可能出现this引用逸出,使用工厂方法可以防止this引用构造过程中逸出。
容器的隐藏迭代,如调用toString、hashCode和equals方法时会隐式的进行迭代,要对所有进行迭代的地方进行加锁处理。
CopyOnWriteArrayList,写入时复制,每次修改时都会创建并重新发布一个新的容器副本。
阻塞队列和生产者-消费者模式,应用:线程池和工作队列的组合。
同步工具类(线程间通信的方式):
阻塞队列、信号量(Semaphore)、栅栏(Barrier)和闭锁(Latch)。
1.闭锁(Latch),用以确保某个服务所需要的其他服务都已经启动才进行或确保某个操作的所有参与者都就绪再进行。CountDownLatch包含一个计数器,初始为一个整数,countDown方法递减计数器,表示有一个时间已发生,await方法会阻塞等待计数器值为0,或者等待线程中断、等待超时。
public class TestHarness {
public long timeTasks(int nThreads, final Runnable task)
throws InterruptedException {
final CountDownLatch startGate = new CountDownLatch(1);
final CountDownLatch endGate = new CountDownLatch(nThreads);
for (int i = 0; i < nThreads; i++) {
Thread t = new Thread() {
public void run() {
try {
startGate.await();
try {
task.run();
} finally {
endGate.countDown();
}
} catch (InterruptedException ignored) {
}
}
};
t.start();
}
long start = System.nanoTime();
//startGate确保所有线程同时启动
startGate.countDown();
endGate.await();
long end = System.nanoTime();
return end - start;
}
}
2.计数信号量用来控制对某个特定资源的操作数量或某种指定操作的数量,Semaphore管理着一组虚拟许可,acquire会阻塞直到获取许可(或被中断或操作超时),release方法将返回一个许可信号量。
3.栅栏类似于闭锁,能阻塞到一组线程直到某个事件发生,且可重用,闭锁是一次性对象。
高效的可伸缩结果缓存,使用FutureTask来查询每次要查询的值是否已计算完毕,若没有则阻塞,get方法,使用putIfAbsent方法确保不会重复放入相同的FutureTask。
import java.util.concurrent.*;
public class Memoizer <A, V> implements Computable<A, V> {
private final ConcurrentMap<A, Future<V>> cache
= new ConcurrentHashMap<A, Future<V>>();
private final Computable<A, V> c;
public Memoizer(Computable<A, V> c) {
this.c = c;
}
public V compute(final A arg) throws InterruptedException {
while (true) {
Future<V> f = cache.get(arg);
if (f == null) {
Callable<V> eval = new Callable<V>() {
public V call() throws InterruptedException {
return c.compute(arg);
}
};
FutureTask<V> ft = new FutureTask<V>(eval);
f = cache.putIfAbsent(arg, ft);
if (f == null) {
f = ft;
ft.run();
}
}
try {
return f.get();
} catch (CancellationException e) {
cache.remove(arg, f);
} catch (ExecutionException e) {
throw LaunderThrowable.launderThrowable(e.getCause());
}
}
}
}