实战Java高并发程序设计(第二版)-chp4锁

2023-11-05

多线程引用:需要维护并行数据结构间的一致性状态,需要为线程的切换和调度花费时间。

参考

4.1 合理的锁性能

4.1.1 减少锁持有时间

原有的程序:对整个方法做同步,导致等待线程大量增加;

​ 因为一个线程,在进入该方法时获得内部锁,只有所有任务都执行完后,才会释放锁;

public synchronized void syncMethod(){
    othercode1();
    mutexMethod();
    othercode2();
}

优化的方案:只在必要时进行同步,能减少线程持有锁的时间

public void syncMethod2(){
    othercode1();
    synchronized(this){
        mutexMethod();
    }
    othercode2();
}

​ 只针对mutexMethod()方法进行了同步;

4.1.2 减小锁粒度

分析ConcurrentHashMap

减小锁粒度:缩小锁定对象的范围,从而降低锁冲突的可能性,进而提高系统的并发能力;

4.1.3 用读写分离锁来替换独占锁

ReadWriteLock读写锁:

减小锁粒度:通过分割数据结构来实现的

读写分离锁:对系统功能点的分割

读操作本身不会影响数据的完整性和一致性;在读多写少的场合使用读写锁可有效提升系统的并发能力;

4.1.4 锁分离

读写锁:根据读写操作功能上的不同,进行了有效的锁分离。

依据程序的功能特点,使用类似的分离思想可对独占锁进行分离:LinkedBlockingQueue

LinkedBlockingQueue:

​ take()方法:使用takeLock,只在get操作中使用;

    public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();
        try {
            while (count.get() == 0) {
                notEmpty.await();
            }
            x = dequeue();
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }

​ put()方法:使用putLock,只在put操作中使用;

    public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        // Note: convention in all put/take/etc is to preset local var
        // holding count negative to indicate failure unless set.
        int c = -1;
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();
        try {
            /*
             * Note that count is used in wait guard even though it is
             * not protected by lock. This works because count can
             * only decrease at this point (all other puts are shut
             * out by lock), and we (or some other waiting put) are
             * signalled if it ever changes from capacity. Similarly
             * for all other uses of count in other wait guards.
             */
            while (count.get() == capacity) {
                notFull.await();
            }
            enqueue(node);
            c = count.getAndIncrement();
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
    }

take()方法和put()方法相互独立,之间不存在锁竞争;只在take()和take()方法之间,put()和put()方法之间存在竞争。从而削弱了锁竞争的可能性;

通过takeLock和putLock两把锁,LinkedBlockingQueue实现了取数据和读数据的分离;

4.1.5 锁粗化

锁粗化:虚拟机在遇到一连串连续地对同一个锁不断进行请求和释放操作时,便会把所有的锁整合成对锁的一次请求,从而减少对锁的请求同步的次数;

锁粗化之前:

public void demoMethod(){
    synchronized(lock){
        // do sth.
    }
    // 做其他不需要的同步的工作,但很快能执行完毕
    synchronized(lock){
        // do sth.
    }
}

锁粗化之后:

public void demoMethod(){
    synchronized(lock){
        // do sth.
        //做其他不需要的同步的工作,但很快能执行完毕
    }
}

锁粗化 的思想和 减少锁持有时间 是相反的;

4.2 JVM对锁优化的支持

JDK内部的锁优化策略

4.2.1 锁偏向

4.3 ThreadLocal

ThreadLocal 是一个线程的局部变量,只有当前线程可以访问;自然是线程安全的

1、线程不安全的例子

/**
 * SimpleDateFormat不是线程安全的,在线程池中共享这个对象会导致错误。
 *
 */
public class ThreadLocaLDemo {
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static class ParseDate implements Runnable{
        int i = 0;
        public ParseDate(int i){this.i=i;}

        @Override
        public void run() {
            try {
                Date t = sdf.parse("2020-07-23 22:46:"+i%60);
                System.out.println(i+":"+t);
            }catch (ParseException e){
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 1000; i++) {
            es.execute(new ParseDate(i));
        }
    }
}

2、使用ThreadLocal未每一个线程创造一个SimpleDaetformat对象实例

/**
 * SimpleDateFormat不是线程安全的,在线程池中共享这个对象会导致错误。
 * ThreadLocal只起到了简单的容器作用;
 * 为每一个线程分配一个对象的工作不是由ThreadLocal来完成的,而是需要在应用层面来保证的。
 * 如果在应用上为每一个线程分配了相同的对象实例,那么ThreadLocal也不能保证线程安全。
 *
 */
public class ThreadLocaLDemo2 {
    static ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>();
    public static class ParseDate implements Runnable{
        int i = 0;
        public ParseDate(int i) {this.i=i;}

        @Override
        public void run() {
            try {
                // 如果当前线程不持有SimpleDateformat对象实例,
                // 就新建一个并把它设置到当前线程中
                if (tl.get() == null) {
                    tl.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));;
                }
                // 若已经持有则直接使用
                Date t =tl.get().parse("2020-07-23 22:46:"+i%60);
                System.out.println(i+":"+t);
            }catch (ParseException e){
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 1000; i++) {
            es.execute(new ParseDate(i));
        }

    }
}

4.3.2 ThreadLocal的实现原理

java 引用类型:

ThreadLocal类的 ThreadLocalMap类

     

4.3.3 测试使用ThreadLocal时的性能

package HIighParallel.chp4.p3;

import java.util.Random;
import java.util.concurrent.*;

public class RandMultiThread {
    // 定义了每个线程要产生随机数数量
    public static final int GEN_COUNT = 10000000;
    // 定义了参与工作的线程数量
    public static final int THREAD_COUNT = 4;
    // 定义了线程池
    static ExecutorService exe = Executors.newFixedThreadPool(THREAD_COUNT);
    // 定义:被多线程共享的Random实例,用于产生随机数
    public static Random rnd = new Random(123);
    // 定义了由ThreadLocal封装的Random
    public static ThreadLocal<Random> tRnd = new ThreadLocal<Random>(){
        @Override
        protected Random initialValue() {
            return new Random(123);
        }
    };

    // 定义工作线程的内部逻辑,有两种模式
    // 第一种模式:多线程共享一个Random(mode=0)
    // 第二种模式:多个线程各分配一个Random(mode=1)

    public static class RndTask implements Callable<Long>{
        private int mode = 0;

        public RndTask(int mode){
            this.mode = mode;
        }

        public Random getRandom(){
            if (mode == 0){
                return rnd;
            }else if (mode == 1){
                return tRnd.get();
            }else {
                return null;
            }
        }
        // 定义了线程的工作内容;每个线程都会产生若干个随机数,完成工作后记录所消耗的时间
        @Override
        public Long call() throws Exception {
            long b = System.currentTimeMillis();
            for (long i = 0; i < GEN_COUNT; i++) {
                getRandom().nextInt();
            }
            long e = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + "Spend " + (e-b) + " ms");
            return e - b;
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Future<Long>[] futs = new Future[THREAD_COUNT];
        // 第一种工作模式:多线程共享一个Random(mode=0)
        for (int i = 0; i < THREAD_COUNT; i++) {
            futs[i] = exe.submit(new RndTask(0));
        }
        long totaltime = 0;
        for (int i = 0; i < THREAD_COUNT; i++) {
            totaltime += futs[i].get();
        }
        System.out.println("多线程访问同一个Random实例:" + totaltime + "ms");
		
        // 第二种工作模式:多个线程各分配一个Random(mode=1)
        // ThreadLocal的情况
        for (int i = 0; i < THREAD_COUNT; i++) {
            futs[i] = exe.submit(new RndTask(1));
        }
        totaltime = 0;
        for (int i = 0; i < THREAD_COUNT; i++) {
            totaltime += futs[i].get();
        }
        System.out.println("使用ThreadLocal包装Random实例:" + totaltime + "ms");
        exe.shutdown();
    }
}

4.4 无锁

加锁:悲观的策略;

​ 假设每一次的临界区操作会产生冲突,若遇到多个线程同时访问临界区则让其他线程等待;

无所:乐观的策略;

​ 假设对资源的访问没有冲突,使用CAS技术鉴别线程冲突;

4.4.1 CAS 比较并交换

算法的过程CAS(V, E, N)

​ V:表示要更新的变量

​ E:表示变量的期望值

​ N:表示新值

1)当V值 等于 E值 时,才会将V值修改为N值

​ 当V值不等于E值时,不会进行V值的修改;说明已经有其他线程修改了V值,当前线程什么都不做

2)CAS返回当前V的真实值

3)CAS是一种乐观的策略

4)当多个线程使用CAS操作一个变量时,只有一个能成功修改,其余会失败;失败的线程不会被挂起,仅被告知失败,并且允许再次尝试,也允许失败的线程放弃操作;

CAS的问题

1、ABA问题

​ CAS需要在操作值的时候检查值有没有发生变化,若没有发生变化则更新;

​ 若值从A,变成了B,又变成A,那么CAS进行检查的时候就会认为它的值没有变化,但实际上变化了

解决思路:使用版本号(JUC包中的AtomicStampedReference类)

2、循环时间长开销大

​ 如果CAS不成功,则会原地自旋,会有开销存在;

3、只能保证一个共享变量的原子操作

​ CAS无法用于对多个共享变量的操作;

4.4.2 AtomicInteger 无锁的线程安全整数

AtomicInteger

// 无锁的情况下,使用volatile关键字保证线程间的数据的可见性;这样在获取变量时才能直接读取
private volatile int value;

incrementAndGet()方法

public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

unsafe.getAndAddInt()方法

/**
var1:待更新的成员变量的对象
var2:成员变量的偏移
var4:要增加多少
*/
public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
    	// do while 自旋操作
        do {
            // 获取AtomicInteger对象var1,在内存中偏移量未var2处的值;
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

unsafe.getIntVolatile() 方法

// getIntVolatile方法获取:对象中offset偏移地址对应的整型field的值,支持volatile load语义
public native int getIntVolatile(Object var1, long var2);

unsafe.compareAndSwapInt()方法

    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

/**
compareAndSwapInt():是一个本地方法
逻辑类似于:
	if( this == expect){
		this = update
		return true;
	}else{
		return false;
	}

*/

4.4.4 无锁的对象引用:AtomicReference

ABA问题

AtomicInteger:对整数的封装;

AtomicReference:对普通的对象的引用;

package HIighParallel.chp4.p4;
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceDemo {
    static AtomicReference<Integer> money = new AtomicReference<Integer>();

    public static void main(String[] args) {
        money.set(19);;
        // 模拟多个线程同时更新后台数据库,未用户充值
        for (int i = 0; i < 3; i++) {
            new Thread(){
                @Override
                public void run() {
                    while (true){
                        while (true){
                            Integer m = money.get();
                            // 判断账户余额并给予赠送金额,如果已被处理则当前线程就会失败
                            if (m<20){
                                if(money.compareAndSet(m, m+20)){
                                    System.out.println("余额小于20元,充值成功,余额:"+money.get()+"元");
                                    break;
                                }else {
                                    break;
                                }
                            }
                        }
                    }
                }
            }.start();
        }

        // 用户消费线程,模拟消费行为
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    while (true){
                        Integer m = money.get();
                        // 赠与金额到账的同时,客户进行了一次消费,总金额小于20,正好累计消费了20元。
                        // 使得消费、赠与后的金额等于消费前、赠与前的金额,那么就会后台的赠与进程就会误认为这个账户还没有赠与,存在多次赠与的可能;
                        if (m>10){
                            System.out.println("大于10元");
                            if (money.compareAndSet(m, m-18)){
                                System.out.println("成功消费10元,余额:"+money.get());
                                break;
                            }
                        }else {
                            System.out.println("没有足够的金额");
                            break;
                        }
                    }
                }try {
                    Thread.sleep(100);
                }catch (InterruptedException e){

                }
            }
        }.start();
    }
}

4.4.5 带有时间戳的对象引用:AtomicStampedReference

AtomicReference:进行CAS时仅比较对象的值;

AtomicStampedReference: 带有时间戳的对象引用,进行CAS时不仅比较对象的值,还比较时间戳;

package HIighParallel.chp4.p4;

import java.util.concurrent.atomic.AtomicStampedReference;

public class AtomicStampedReferenceDemo {
    static AtomicStampedReference<Integer> money = new AtomicStampedReference<Integer>(19, 0);
    public static void main(String[] args) {
        // 模拟多个线程更新后台数据库,为用户充值
        for (int i = 0; i < 3; i++) {
            /**
             * 获得账户的时间戳;
             *      若赠与成功,则修改时间戳,使得系统不可能发生二次赠与;
              */
            final int timestamp = money.getStamp();
            new Thread(){
                @Override
                public void run() {
                    while (true){
                        while (true){
                            Integer m = money.getReference();
                            if (m<20){
                                // 赠与成功就将时间戳加1;
                                if (money.compareAndSet(m, m+20, timestamp, timestamp+1)){
                                    System.out.println("余额小于20元, 充值成功, 余额:"+money.getReference()+"元");
                                    break;
                                }
                            }else {
                                break;
                            }
                        }
                    }
                }
            }.start();
        }
        // 用户消费进程,模拟消费行为
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    while (true){
                        int timestamp = money.getStamp();
                        Integer m = money.getReference();
                        if (m>10){
                            System.out.println("大于10元");
                            if (money.compareAndSet(m, m-10, timestamp, timestamp+1)){
                                System.out.println("成功消费10元,余额: "+money.getReference());
                                break;
                            }
                        }else {
                            System.out.println("没有足够的金额");
                            break;
                        }
                    }
                    try {
                        Thread.sleep(100);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }
}

4.4.6 数组无锁:AtomicIntegerArray

JUC可用的原子数组有:AtomicIntegerArray、AtomiclongArray、AtomicReferenceArray

​ 分别表示整数数组、long型数组、普通的对象数组;

AtomicIntegerArray

​ 本质是对int[ ] 类型的封装;使用Unsafe类通过CAS的方式控制int[ ]在多线程下的安全性;

package HIighParallel.chp4.p4;

import java.util.concurrent.atomic.AtomicIntegerArray;

public class AtomicIntegerArrayDemo {
    // 声明了一个包含10个元素的原子整数数组
    static AtomicIntegerArray arr = new AtomicIntegerArray(10);
    // 该线程类型功能:对数组内10个元素进行累加操作
    public static class AddThread implements Runnable{
        @Override
        public void run() {
            for (int k = 0; k < 10000; k++) {
                arr.getAndIncrement(k%arr.length());
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread[] ts = new Thread[10];
        // 开启十个累计线程
        for (int k = 0; k < 10; k++) {
            ts[k] = new Thread(new AddThread());
        }
        for (int k = 0; k < 10 ; k++) { ts[k].start(); }
        for (int k = 0; k < 10; k++) { ts[k].join(); }
        System.out.println(arr);
    }
}

4.4.7 原子普通变量:AtomicIntegerFieldUpdater

软件设计原则之一:

  • 开闭原则:系统对功能的增加应该是开放的,而对修改是保守的;

AtomicIntegerFieldUpdater:

​ 让普通变量也能有CAS操作,从而带来线程安全性;

Updater有三种:

AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater

package HIighParallel.chp4.p3;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class AtomicIntegerFieldUpdaterDemo {
    public static class Candidate{
        int id;
        volatile int score;
    }
    public final static AtomicIntegerFieldUpdater<Candidate> scoreUpdater =
            AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score");
    // 检查Updater是否正确工作
    public static AtomicInteger allScore = new AtomicInteger(10);

    public static void main(String[] args) throws InterruptedException {
        final Candidate stu = new Candidate();
        Thread[] t = new Thread[10000];
        for (int i = 0; i < 10000; i++) {
            t[i] = new Thread(){
                @Override
                public void run() {
                    if (Math.random()>0.4) {
                        scoreUpdater.incrementAndGet(stu);
                        allScore.incrementAndGet();
                    }
                }
            };
            t[i].start();

        }
        for (int i = 0; i < 10000; i++) {
            t[i].join();
        }
        System.out.println("score="+stu.score);
        System.out.println("allScore="+allScore);
    }
}

AtomicIntegerFieldUpdater:

  • Updater只能修改它可见范围内的变量,因为Updater使用反射得到这个变量;
  • 变量必须是volatile类型。保证可见性
  • CAS操作通过对象实例在内存中的偏移量进行复制,因此它不支持static字段;

4.4.8 无锁的Vector

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

实战Java高并发程序设计(第二版)-chp4锁 的相关文章

随机推荐

  • windows10配置远程桌面多用户同时登录

    目录 一 单用户同时登录 二 多用户同时登录 一 单用户同时登录 系统属性 gt 远程 勾选以下选项 运行 gpedit msc 选择 计算机配置 gt 管理模板 gt Windows组件 gt 远程桌面服务 gt 远程桌面会话主机 gt
  • 秒杀系统架构优化思路

    秒杀系统架构优化思路 上周参加Qcon 有个兄弟分享秒杀系统的优化 其观点有些赞同 大部分观点却并不同意 结合自己的经验 谈谈自己的一些看法 一 为什么难 秒杀系统难做的原因 库存只有一份 所有人会在集中的时间读和写这些数据 例如小米手机每
  • logback.xml文件未被加载

    起初 将logback xml放到了src下面 结果运行后发现只能在控制台输出日志 而不能将日志输出到文件中 于是网上搜索 首先将logback xml root标签中的ALL改为OFF 再次运行程序 看是否能够加载logback xml文
  • Katex的markdown常用语法中一些关于Latex数学符号或公式等的笔记

    文章目录 数学符号 设变量时常用的希腊字母 大小关系 分数 开方 同余 一般符号 二项式 符号上下添加额外信息 上标符号 上下标 上下划线 箭头 集合 省略号 矩阵 小括号形式 中括号形式 行列式 带省略号的形式 带横线或竖线分隔的形式 逻
  • CentOS7编译安装Openvswitch 2.3.0 LTS

    1 安装依赖包 yum y install openssl devel wget kernel devel 2 安装开发工具 yum groupinstall Development Tools 3 添加用户 adduser ovswitc
  • c++ sdk框架_鸿蒙系统中的 JS 开发框架

    今天鸿蒙终于发布了 开发者们也终于 沸腾 了 源码托管在国内知名开源平台码云上 https gitee com openharmony 我也第一时间下载了源码 研究了一个晚上 顺带写了一个 hello world 程序 还顺手给鸿蒙文档提了
  • Flutter lottie开机启动动画

    一款app在启动预加载数据时 少不了采用开机启动动画方案 今天介绍lottie制作开机启动动画 Lottie官网地址 https lottiefiles com 项目源码 Flutter手机端 lottie实现开机启动动画源码 前端元素由前
  • ps背景不变换字_ps怎么把背景上面的字换掉

    1 怎么用ps把图片上的字换掉 换成自己想打的字 1 演示使用的设计软件为photoshop 版本为Adobe photoshop CC2017 以下简称PS 2 打开图像处理软件PS 然后加载一张用于演示换字的图片 3 首先选择工具栏中的
  • 静态代码和动态代码的区别_静态代码扫描方法及工具介绍

    来自 信安之路 微信号 xazlsec 本文作者 国勇 信安之路特约作者 静态扫描就是不运行程序 通过扫描源代码的方式检查漏洞 常见的方法也有多种 如把源代码生成 AST 抽象语法树 后对 AST 进行分析 找出用户可控变量的使用过程是否流
  • 限制服务器显示ip段,限制网段中的某段IP访问Samba服务器

    今天在百度知道看到这样一个问题 linux samba服务如何限制指定网段访问 例如 允许192 168 1 0网段访问 但不允许192 168 1 100 192 168 1 170访问 这个问题与我之前的一篇文章 Samba访问控制 貌
  • Android开发edittext输入监听

    在开发Android的过程中 对于edittext的使用频率还是挺高的 比如用户账号密码的输入 基本信息的填写 数据的填入等 一般都会通过button点击事件对其数据进行提取 不过在一些场景 需要实时监听或者当输入完毕之后要马上获取用户所输
  • 逻辑回归和SVM的区别

    1 LR采用log损失 SVM采用合页损失 2 LR对异常值敏感 SVM对异常值不敏感 3 在训练集较小时 SVM较适用 而LR需要较多的样本 4 LR模型找到的那个超平面 是尽量让所有点都远离他 而SVM寻找的那个超平面 是只让最靠近中间
  • FFmpeg从入门到入魔(2):保存流到本地MP4

    1 FFmpeg裁剪移植 之前我们简单地讲解了下如何在Linux系统中编译FFmpeg 但是编译出来的so体积太大 而且得到的多个so不便于使用 本节在此基础上 将详细讲解在编译FFmpeg时如何对相关模块作裁剪以精简so的体积 并且编译只
  • 文档权限服务器上,服务器上的权限

    服务器上的权限 内容精选 换一换 数据库安全服务与其他云服务的关系的依赖关系如图1所示 数据库安全服务实例创建在弹性云服务器上 用户可以通过该实例 为弹性云服务器上的自建数据库提供安全审计功能 数据库安全服务可以为关系型数据库服务中的RDS
  • Css:Conditional comments(条件注释)

    http msdn microsoft com en us library ms537512 VS 85 aspx http www quirksmode org css condcom html Item Example Comment
  • 23黑马QT笔记之猜数字游戏答案

    23黑马QT笔记之猜数字游戏答案 代码在自己写的day04的第一个项目 想要代码的直接评论 写上自己的邮箱 不要像以前发私信了 因为CSDN有时消息不同步 或者看了之后忘了
  • Flask基础: 增删改查

    app py 实现数据库的增删改查 from flask import make response request session abort jsonify from config settings import Config from
  • 隐藏导航条底部的黑线(shadowImage)四种办法

    方法一 当设置navigationBar的背景图片时移除黑线的方法 该方法会使translucent属性失效 objc view plain copy 方法一 当设置navigationBar的背景图片时移除黑线的方法 该方法会使trans
  • Python操作redis及redis的操作命令

    Python操作redis及redis的操作命令 Python操作redis https www cnblogs com melonjiang p 5342505 html https www cnblogs com xiaojing201
  • 实战Java高并发程序设计(第二版)-chp4锁

    多线程引用 需要维护并行数据结构间的一致性状态 需要为线程的切换和调度花费时间 参考 实战Java高并发程序设计 第二版 Unsafe类详解 java cas算法实现乐观锁 4 1 合理的锁性能 4 1 1 减少锁持有时间 原有的程序 对整