以下内容大多来自周志明的《深入理解Java虚拟机》。
1. 线程本地存储Thread Local Storage
一个请求对应一个服务器线程,很多web服务端应用都可以使用线程本地存储来解决线程安全问题。每一个线程的Thread对象中都有一个ThreadLocalMap对象,这个对象存储了一组以ThreadLocal.threadLocalHashCode为键,以本地线程变量为值的K-V值对,ThreadLocal对象就是当前线程的ThreadLocalMap的访问入口,每一个ThreadLocal对象都包含了一个独一无二的threadLocalHashCode值,使用这个值就可以在线程k-v值对中找回对应的线程变量。
我们可以使用ThreadLocal把上下文信息存储的线程本地存储中,而不会和其他线程冲突。
2. 原子性
public static AtomicInteger race = new AtomicInteger(0); public static void increase() { race.incrementAndGet(); } private static final int ThreadsCnt = 20; public static void main(String[] args) { Thread[] threads = new Thread[ThreadsCnt]; for (int i = 0; i < ThreadsCnt; i++) { threads[i] = new Thread(new Runnable() { @Override public void run() { for(int j = 0; j < 10000; j++) { increase(); } } }); threads[i].start(); } while (Thread.activeCount() > 1) { Thread.yield(); } System.out.println(race); }
输出:
200000
incrementAndGet原子操作,20个线程,每个线程1000次操作后,最后的结果是20000,保证了正确的结果。
尽管CAS看起来很美,但是这种操作无法涵盖互斥同步的所有使用场景,并且CAS从语义上来说并不是完美的,存在这样一个逻辑漏洞:
如果一个变量V初次读取的时候是A值,那我们就能说它的值没有被其他线程变过吗?如果这段期间它的值被改成了B,后来又被改回A,那CAS操作就会误认为它从来没有被改变过。这个漏洞称为CAS操作的ABA问题。J.U.C包为了解决这个问题,提供了一个带有标记的原子引用类"AtomicStampedReference",它可以通过控制变量值的版本来保证CAS的正确性。不过目前来说这个类比较鸡肋,大部分情况下ABA问题不会影响程序并发的正确性,如果需要解决ABA的问题,改用传统的互斥同步可能会比原子类更高效。
以上内容来自原著,但是原著里incrementAndGet方法的源代码和我测试时候的源代码不同,1.7和1.8中的实现方式已经不同了,1.8是:
/** * Atomically increments by one the current value. * * @return the updated value */ public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; }
之所以提到原子操作是因为原著提到了线程安全的实现方式。
1. 互斥同步 悲观锁
在java中,最基本的互斥同步手段就是synchonized关键词。原著中还提到了一个java.util.concurrent包中的重入锁ReentrantLock,有一点区别,这里就不陈述了。参考资料:http://tenyears.iteye.com/blog/48750
2. 非阻塞同步 乐观锁
需要硬件支持。乐观锁就是通过原子操作来实现的。