Java并发编程的艺术-笔记-第二章

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。


版权声明:本文为o_ogou原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。