Java代码 --> Java字节码 --> JVM --> 字节码 --> 汇编指令
volatile
- volatile 是轻量级的 synchronized,保证共享变量的可见性。
- 如果一个字段被声明称volatile,Java线程内存模型保证所有线程看到这个变量是一致的。
- 为了提高处理效率,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存(L1,L2等)。所以具体的实现缓存一致性协议,每个处理器嗅探总线上的数据检查自己缓存值是否过期。所以,多处理器下,一个处理器的缓存写回内存会导致其他处理器的缓存无效。
synchronized
- 重量级锁,锁存储在Java对象的对象头中。对象头中有线程ID,偏向锁标志位(1bit),锁标志位(2bit)等。
- 锁的级别由低到高分别是 无锁 --> 偏向锁 --> 轻量级锁 --> 重量级锁。所可以升级但不能降级。
- 普通方法锁住是实例对象,静态方法锁住当前类的Class对象,同步方法块锁住括号中配置的对象。
偏向锁
- 一个线程访问同步块并获取锁时,在对象头和栈帧中记录偏向的线程的ID。之后该线程进入退出同步块不需要通过CAS来加锁和解锁。
- 具体步骤时测试对象头MarkWord中ID是否是当前线程的ID,是则表示获得锁。否则测试偏向锁标志位是否是1,不是1则使用CAS竞争锁。是1,则尝试将对象头的偏向锁指向当前线程。
- 是一种等到竞争出现才释放锁的机制。
轻量级锁
- 加锁:
将对象头的MarkWord复制到锁记录中(成为displaced mark word)。尝试用CAS将对象头的Mark Word替换为指向锁记录的指针,成功则获得锁,失败则表示其他线程在竞争,使用自旋(不断循环尝试)来获得锁。
- 解锁
使用原子的CAS将Displaced Mark Word替换对象头。如果成功表示没有竞争发生。如果失败,表示存在竞争。锁会膨胀为重量级锁。
比较
- 偏向锁如果存在锁竞争,会产生锁撤销的消耗,适合一个线程访问同步块的场景。
- 轻量级锁竞争失败的线程在自旋的时候会消耗cpu。适合同步块执行非常快的场景。
- 重量级锁,速度慢,适合同步块执行时间长的场景。
使用锁和循环CAS实现原子操作。
package _14Currency.Chapter2;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
public class Chapter2 {
private AtomicInteger atomicInteger = new AtomicInteger(0);
private int i = 0;
public static void main(String[] args) {
final Chapter2 cas = new Chapter2();
List<Thread> threads = new ArrayList<>(600);
long start = System.currentTimeMillis();
for (int j = 0; j < 100; j++) {
Thread t = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
cas.count();
cas.safeCount();
}
});
threads.add(t);
}
for (Thread t : threads) t.start();
for (Thread t : threads) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(cas.i);
System.out.println(cas.atomicInteger.get());
System.out.println(System.currentTimeMillis() - start);
}
private void safeCount() {
while (true) {
int i = atomicInteger.get();
boolean success = atomicInteger.compareAndSet(i, ++i);
if(!success) System.out.println("fail");
if (success) break;
// 通过cas实现线程安全计数器
}
}
private void count() {
i++;
}
}
- JDK1.5开始提供AtomicBoolean,AtomicLong等来支持原子操作。这些类还提供一些工具方法。比如atomicInteger.getAndAdd(1);就可以替代safeCount()方法中的内容。
- 上诉CAS实现的问题:
- ABA问题:增加版本号解决。JDK的Atomic包中提供了AtomicStampedReference类来解决ABA问题。
- 循环开销大:pause指令解决
- 只能保证一个共享变量的原子操作。通过AtomicReference类保证对象之间的原子性(先将多个对象放到一个对象中)
JVM内部除了偏向锁,其他实现锁的方式都是循环CAS。