JMM内存模型深入详解,探索volatile、synchronized与VarHandle深层次的奥秘

news2024/11/25 19:47:13

文章目录

  • 一、JMM内存模型
    • 1、什么是JMM
      • (1)参考资料
    • 2、竞态条件(Race Condition)
      • (1)实例
    • 3、同步动作(Synchronization Order)
      • (1)实例
      • (2)实例:SO 并不是阻止多线程切换
      • (3)实例:volatile 只用了一半算 SO 吗?
        • ① 实例1
        • ② 实例2
        • ③ 实例3
    • 4、Happens-Before
      • (1)具体规则
      • (2)案例
    • 5、因果律(Causality)
    • 6、安全发布
  • 二、内存屏障
    • 1、LoadLoad
    • 2、LoadStore
    • 3、StoreStore
    • 4、StoreLoad(*)
    • 5、Acquire
    • 6、Release
    • 7、小总结
  • 三、volatile
    • 1、volatile的本质
    • 2、可见性(visibility)
      • (1)分析所有可能性
      • (2)解决方案
    • 3、有序性: 共享变量部分被volatile修饰:partial ordering
      • (1)实例:重排序导致不可预料的结果
      • (2)解决方案:volatile修饰y
      • (3)分析:volatile 修饰 x - 行不行
      • (4)总结
    • 4、有序性:共享变量全部被volatile修饰:total ordering
      • (1)实例:重排序导致不可预料的结果
      • (2)volatile 仅修饰 y - 不符合最后写最先读
      • (3)volatile 修饰 x 和 y - 不符合最后写最先读
  • 四、Synchronized
    • 1、Synchronized的本质
    • 2、monitorenter 与 monitorexit 工作原理
    • 3、Synchronized内存屏障
    • 4、Synchronized正确使用
    • 5、Synchronized的优化
    • 6、无锁 vs 有锁
  • 五、VarHandle

一、JMM内存模型

1、什么是JMM

A memory model describes, given a program and an execution trace of that program, whether the execution trace is a legal execution of the program. A high level, informal overview of the memory model shows it to be a set of rules for when writes by one thread are visible to another thread.

简单理解JMM内存模型:多线程下,共享变量的读写顺序是头等大事,内存模型就是多线程下对共享变量的一组读写规则

也就是说,JMM主要关注共享变量是否在线程间同步代码可能得执行顺序。也就是我们常说的,可见性与指令重排序的问题。

需要关注的操作就有两种:Load、Store:
Load就是从缓存读取到寄存器中,如果一级缓存中没有,就会层层读取二级、三级缓存,最后才是Memory。
Store 就是从寄存器运算结果写入缓存,不会直接写入 Memory,当 Cache line 将被 eject 时,会writeback 到 Memory。

(1)参考资料

Java Language Specification Chapter 17. Threads and Locks
JSR-133: JavaTM Memory Model and Thread Specification
Doug Lea’s JSR-133 cookbook
Sutter’s Mill atomic Weapons: The C++ Memory Model and Modern Hardware
Paul E. Mckenney’s Is Parallel Programming Hard, And, If So, What Can You Do About It?Appendix C - Why Memory Barriers?
jcstress
Aleksey Shipilёv’s Java Memory Model Pragmatics (transcript)
Java Concurrency in Practice
The Art of Multiprocessor Programming

2、竞态条件(Race Condition)

在多线程下,没有依赖关系的代码,在执行共享变量读写操作(至少有一个线程写)时,并不能保证以编写顺序(Program Order)执行,这称为发生了竞态条件(Race Condition)。

(1)实例

例如:有共享变量 x,线程 1 执行

r.r1 = y;
r.r2 = x;

线程 2 执行:

x = 1;
y = 1;

最终的结果可能是 r11 而 r20(指令重排序的结果):

y = 1;
r.r1 = y;
r.r2 = x;
x = 1;

有同学这个时候会有疑问了,既然产生的结果有可能不是我们预期的,为什么还要进行指令重排序呢?答案就是竞争是为了更好的性能 -Data Race Free

3、同步动作(Synchronization Order)

若要保证多线程下,每个线程的执行顺序(Synchronization Order)按编写顺序(Program Order)执行,那么必须使用 Synchronization Actions 来保证,这些 SA 有:

  • lock,unlock - synchronized, ReentrantLock
  • volatile 方式读写变量 - 保证可见性,防止重排序
  • VarHandle 方式读写变量(比volatile更轻量,jdk9新增)

Synchronization Order 也称之为 Total Order

(1)实例

例如:用 volatile 修饰共享变量 y,线程 1 执行

r.r1 = y;
r.r2 = x;

线程 2 执行:

x = 1;
y = 1;

最终的结果就不可能是 r11 而 r20。

(2)实例:SO 并不是阻止多线程切换

错误的认识,线程 1 执行:

synchronized(LOCK) {
	r1 = x; //1 处
	r2 = x; //2 处
}

线程 2 执行:

synchronized(LOCK) {
	x = 1
}

并不是说 //1 与 //2 处之间不能切换到线程 2,只是即使切换到了线程 2,因为线程 2 不能拿到 LOCK 锁导致被阻塞,执行权又会轮到线程 1。

(3)实例:volatile 只用了一半算 SO 吗?

① 实例1

// 初始化:
int x;
volatile int y;

// 代码执行:
x = 10; //1 处
y = 20; //2 处

此时 //1 处代码绝不会重排到 //2 处之后(只写了 volatile 变量)

② 实例2

int x;
volatile int y;

执行下面的测试用例:

@Actor // 线程1
public void a1(II_Result r) {
	y = 1; //1 处
	r.r2 = x; //2 处
}
@Actor // 线程2
public void a2(II_Result r) {
	x = 1; //3 处
	r.r1 = y; //4 处
}

//1 //2 处的顺序可以保证(只写了 volatile 变量),但 //3 //4 处的顺序却不能保证(只读了 volatile 变量),仍会出现 r1r20 的问题。

③ 实例3

有时会很迷惑人,例如下面的例子

@Actor // 线程1
public void a1(II_Result r) {
	r.r2 = x; //1 处
	y = 1; //2 处
}
@Actor // 线程2
public void a2(II_Result r) {
	r.r1 = y; //3 处
	x = 1; //4 处
}

这回 //1 //2 (只写了 volatile 变量)//3 //4 处(只读了 volatile 变量)的顺序均能保证了,绝不会出现r1r21 的情况。

此外将用例 2 中两个变量均用 volatile 修饰就不会出现 r1r20 的问题,因此也把全部都用 volatile 修饰称为total order,部分变量用 volatile 修饰称为 partial order

并不是说 partial order 不能用,只是,正确使用需要学明白后面的原理

4、Happens-Before

happens-before规则——理解happens-before规则

Happens-Before 保证了 线程切换时代码的顺序和可见性。

若是变量读写时发生线程切换(例如,线程 1 写入 x,切换至线程 2,线程 2 读取 x)在这些边界的处理上如果有action1 先于 action 2 发生,那么代码可以按确定的顺序执行,这称之为 Happens-Before Order 规则。

Happens-Before Order 也称之为 Partial Order

用公式表达为:在这里插入图片描述
含义为:如果 action1 先于 action2 发生,那么 action1 之前的共享变量的修改对于 action2 可见,且代码按 PO(编写)顺序执行。

(1)具体规则

其中 Tn 代表线程,而 x 未加说明,是普通共享变量,使用 volatile 会单独说明:

1)线程的启动和运行边界
start()规则:如果线程 A 执行操作 ThreadB.start()(启动线程 B),那么 A 线程的ThreadB.start()操作 happens-before 于线程 B 中的任意操作。
在这里插入图片描述
2)线程的结束和 join 边界
join()规则:如果线程 A 执行操作 ThreadB.join()并成功返回,那么线程 B 中的任意操作 happens-before 于线程 A 从 ThreadB.join()操作成功返回。
在这里插入图片描述
3)线程的打断和得知打断边界
程序顺序规则:一个线程中的每个操作,happens-before 于该线程中的任意后续操作。
在这里插入图片描述
4)unlock 与 lock 边界
监视器锁规则:对一个锁的解锁,happens-before 于随后对这个锁的加锁。
在这里插入图片描述
5)volatile write 与 volatile read 边界
volatile 变量规则:对一个 volatile 域的写,happens-before 于任意后续对这个volatile 域的读。
在这里插入图片描述
6)传递性
传递性:如果 A happens-before B,且 B happens-before C,那么 A happens-before C。
在这里插入图片描述

(2)案例

// 永不终止的循环 - 可见性问题
public class TestInfinityLoop {
    volatile static boolean stop = false; //停止标记 可以使用volatile实现共享变量的可见性
    static final Object lock = new Object(); // 可以使用lock、synchronized 等锁实现可见性

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stop = true; // volatile 的写
        });
        System.out.println("start " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
        t.start();
        t.join(); // 可以使用join,来实现共享变量的可见性
        foo();
    }

    private static void foo() {
        while (true) {
            boolean b = stop; // volatile 的读
            if (b) {
                break;
            }
        }
        System.out.println("end " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
    }
}

5、因果律(Causality)

Causality 即因果律:代码之间如存在依赖关系,即使没有加 SA(加锁) 操作,代码的执行顺序也是可以预见的。

什么是依赖关系?

@JCStressTest
@Outcome(id = {"0, 0"}, expect = Expect.ACCEPTABLE, desc = "ACCEPTABLE")
@Outcome(expect = Expect.FORBIDDEN, desc = "FORBIDDEN")
@State
public static class Case5 {
	int x;
	int y;
	@Actor // 线程1
	public void a1(II_Result r) {
		r.r1 = x;
		y = r.r1;
	}
	@Actor // 线程2
	public void a2(II_Result r) {
		r.r2 = y;
		x = r.r2;
	}
}

x 的值来自于 y,y 的值来自于 x,而二者的初始值都是 0,因此没有可能有其他结果。

6、安全发布

若要安全构造对象,并将其共享使用,需要用 final 或 volatile 修饰其成员变量,并避免 this 溢出情况

静态成员变量可以安全地发布

例如:

class Holder {
	int x1;
	volatile int x2;
	
	public Holder(int v) {
		x1 = v;
		x2 = v;
	}
}

需要将它作为全局使用:

Holder f;

两个线程,一个创建,一个使用:

@Actor // 线程1
public void a1() {
	f = new Holder(1);
}
@Actor // 线程2
void a2(I_Result r) {
	Holder o = this.f;
	if (o != null) {
		r.r1 = o.x2 + o.x1; // 0
	} else {
		r.r1 =1; // ‐1
	}
}

new Holder(1) 的过程并不是一个原子的操作, 可能会看到未构造完整的对象。

如果想要得到预期的结果,可以在Holder f 加上volatile,或者在Holder中的x2 加上volatile。
注意:在Holder中的x1加上volatile仍然无法解决以上的问题。

二、内存屏障

共有四种内存屏障,具体实现与 CPU 架构相关,不必钻研太深,只需知道它们的效果。

1、LoadLoad

防止 B 的 Load(读) 重排到 A 的 Load(读) 之前。

// 相当于,read(B)不能排到read(A)上面去
read(A)
LoadLoad // (↓)  向下箭头 下面的上不去
read(B)

2、LoadStore

load(A)
LoadStore // (↓) 向下箭头 下面的上不去
Store(B)

防止 B 的 Store(写) 被重排到 A 的 Load(读) 之前

3、StoreStore

A = x
StoreStore
B = true

Store(A)
StoreStore // (↑) 向上箭头 上面的下不去
Store(B)

防止 A 的 Store 被重排到 B 的 Store 之后
意义:在 B 修改为 true 之前,其它线程别想看到 A 的修改。有点类似于 sql 中更新后,commit 之前,其它事务不能看到这些更新(B 的赋值会触发 commit 并撤除屏障)

4、StoreLoad(*)

StoreLoad发生在线程切换时 才有效,Store能够让线程1所有写入都同步到内存,线程2的Load操作能够读到内存中最新的数据。

Store(A) // 线程1
StoreLoad// Store(↑)不能跑下面 + Load(↓)不能跑上面
Load(B) // 线程2

意义:屏障前的改动都同步到主存,屏障后的 Load 获取主存最新数据。
防止屏障前所有的写操作,被重排序到屏障后的任何的读操作,可以认为此 store -> load 是连续的。
有点类似于 git 中先 commit,再远程 poll,而且这个动作是原子的

5、Acquire

Acquire 表示LoadLoad + LoadStore的组合。

load(x) 
LoadLoad + LoadStore // (↓) 向下箭头 下面的上不去
store(z, 10)
load(y)

6、Release

Release表示StoreStore + LoadStore的组合

load(x) 
store(x, 10)
StoreStore + LoadStore // (↑) 向上箭头 上面的下不去
store(y, 10)

7、小总结

LoadLoad + LoadStore = Acquire 即让同一线程内读操作之后的读写上不去,第一个 Load 能读到主存最新值

LoadStore + StoreStore = Release 即让同一线程内写操作之前的读写下不来,后一个 Store 能将改动都写入主存

StoreLoad 最为特殊,还能用在线程切换时,对变量的写操作 + 读操作做同步,只要是对同一变量先写后读,那么屏障就能生效

三、volatile

1、volatile的本质

volatile的本质其实就是不同的内存屏障的组合。
事实上对 volatile 而言 Store-Load,与 LoadLoad 屏障最为有用,简化起见以后的分析省略部分其他屏障。

在这里插入图片描述
volatile保证了:

  • 单一变量的赋值原子性(以前32位操作系统,无法保证long、double等数据的原子性赋值)。
  • 控制了可能的执行路径:线程内按屏障有序,线程切换时按 HB(Happens-Before) 有序。
  • 可见性:线程切换时若发生了 写 ->读 则变量可见,顺带影响普通变量可见。

2、可见性(visibility)

/**
 * 案例1: 测试对同一个变量的多次读操作是否连贯
 */
@JCStressTest
@Outcome(id = {"3, 3, 3", "0, 0, 0"}, expect = Expect.ACCEPTABLE, desc = "ACCEPTABLE")
@Outcome(id = {"0, 3, 3", "0, 0, 3"}, expect = Expect.ACCEPTABLE, desc = "ACCEPTABLE")
@Outcome(id = "0, 3, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "INTERESTING")
@State
public static class Case1 {
    static class Foo {
        int x = 0;
    }

    Foo p = new Foo();
    Foo q = p;

    @Actor // 线程1
    public void actor1(III_Result r) {
        r.r1 = p.x;
        r.r2 = q.x;
        r.r3 = p.x;
    }

    @Actor // 线程2
    public void actor2() {
        p.x = 3;
    }
}

(1)分析所有可能性

初始:
在这里插入图片描述
case1:
在这里插入图片描述
case 2:
在这里插入图片描述
case 3:
在这里插入图片描述
case 4:
在这里插入图片描述
意外情况:
在这里插入图片描述

(2)解决方案

使用 volatile 修饰 x 即可,给 x 上加入 volatile,会阻止编译器对代码的优化,并加入的 StoreLoad 屏障会保证红色线程的写入,对后续蓝色线程的读取可见。

在这里插入图片描述

3、有序性: 共享变量部分被volatile修饰:partial ordering

(1)实例:重排序导致不可预料的结果

@JCStressTest
@Outcome(id = {"0, 0", "1, 1", "0, 1"}, expect = Expect.ACCEPTABLE, desc = "ACCEPTABLE")
@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "INTERESTING")
@State
public static class Case1 {
    int x;
    int y;

    @Actor
    public void actor1() {
        x = 1;
        y = 1;
    }

    @Actor
    public void actor2(II_Result r) {
        r.r1 = y;
        r.r2 = x;
    }
}

意外情况,发生了指令重排序,导致不可预料的结果:
在这里插入图片描述

(2)解决方案:volatile修饰y

如果 y=1 先发生,那么前面的 Store 屏障会阻止 x=1 排下去,而后面的 Load 屏障会阻止后续的两个读操作排上来,结果为 r1r21:
在这里插入图片描述
如果 r.r1=y 先发生,结果为 r1r20:
在这里插入图片描述
x=1 与 r.r2=x 的顺序无法保证:
在这里插入图片描述
x=1 和 y=1 的位置被固定了,结果都是 r10, r21 ,不会出现 case 4 的 r20, r11 情况。
在这里插入图片描述

(3)分析:volatile 修饰 x - 行不行

在这里插入图片描述
r.r1=y 可以越过 LoadLoad 屏障
y=1 可以越过 Store 等屏障
在这里插入图片描述
有可能导致r20, r11 情况。

(4)总结

部分共享变量用volatile修饰时(partial ordering),volatile 写要用来收官(放在最后),volatile 读要用来开篇(放在最前面)

4、有序性:共享变量全部被volatile修饰:total ordering

(1)实例:重排序导致不可预料的结果

public class TestOrderingTotal {
    /**
     * 案例1: 演示 total ordering 存在的问题
     */
    @JCStressTest
    @Outcome(id = {"1, 0", "0, 1", "1, 1"}, expect = Expect.ACCEPTABLE, desc = "ACCEPTABLE")
    @Outcome(id = "0, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "INTERESTING")
    @State
    public static class Case1 {
        int x;
        int y;

        @Actor
        public void a1(II_Result r) {
            y = 1;
            r.r2 = x;
        }

        @Actor
        public void a2(II_Result r) {
            x = 1;
            r.r1 = y;
        }
    }

    /**
     * 案例2: 演示单个 volatile 变量不能解决 total ordering 的情况
     */
    @JCStressTest
    @Outcome(id = {"1, 0", "0, 1", "1, 1"}, expect = Expect.ACCEPTABLE, desc = "ACCEPTABLE")
    @Outcome(id = "0, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "INTERESTING")
    @State
    public static class Case2 {
        int x;
        volatile int y;

        @Actor
        public void a1(II_Result r) {
            y = 1;
            r.r2 = x;
        }

        @Actor
        public void a2(II_Result r) {
            x = 1;
            r.r1 = y;
        }
    }

    /**
     * 案例3: 演示单个 volatile 变量能解决 total ordering 的情况
     */
    @JCStressTest
    @Outcome(id = {"0, 0", "0, 1", "1, 0"}, expect = Expect.ACCEPTABLE, desc = "ACCEPTABLE")
    @Outcome(id = "1, 1", expect = Expect.FORBIDDEN, desc = "FORBIDDEN")
    @State
    public static class Case3 {
        int x;
        volatile int y;

        @Actor
        public void a1(II_Result r) {
            r.r2 = x;
            y = 1;
        }

        @Actor
        public void a2(II_Result r) {
            r.r1 = y;
            x = 1;
        }
    }

    /**
     * 案例4: 演示两个 volatile 变量可以解决 total ordering 存在的问题
     */
    @JCStressTest
    @Outcome(id = {"1, 0", "0, 1", "1, 1"}, expect = Expect.ACCEPTABLE, desc = "ACCEPTABLE")
    @Outcome(id = "0, 0", expect = Expect.FORBIDDEN, desc = "FORBIDDEN")
    @State
    public static class Case4 {
        volatile int x;
        volatile int y;

        @Actor
        public void a1(II_Result r) {
            y = 1;
            r.r2 = x;
        }

        @Actor
        public void a2(II_Result r) {
            x = 1;
            r.r1 = y;
        }
    }
}

意外情况,发生了指令重排序,导致不可预料的结果:
在这里插入图片描述

(2)volatile 仅修饰 y - 不符合最后写最先读

检查最后结果是否可能 r1 r2 都为 0:x=1 可能排下去
在这里插入图片描述

(3)volatile 修饰 x 和 y - 不符合最后写最先读

都是 volatile,意味着它们线程内的次序固定,是可以解决指令重排序的问题。
在这里插入图片描述

四、Synchronized

1、Synchronized的本质

Synchronized本质是通过两个指令:monitorenter 与 monitorexit 来实现的。

public class TestSync {
    static final Object LOCK = new Object();
    static int count;
    public static void main(String[] args) {
        synchronized (LOCK) {
            count++;
        }
    }
}

以上代码我们通过javap -c -v TestSync.class查看,发现有monitorenter和monitorexit:

 public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: getstatic     #7                  // Field LOCK:Ljava/lang/Object;
         3: dup
         4: astore_1
         5: monitorenter
         6: getstatic     #13                 // Field count:I
         9: iconst_1
        10: iadd
        11: putstatic     #13                 // Field count:I
        14: aload_1
        15: monitorexit
        16: goto          24
        19: astore_2
        20: aload_1
        21: monitorexit
        22: aload_2
        23: athrow
        24: return

2、monitorenter 与 monitorexit 工作原理

我们都知道synchronized是对象锁,对锁住的代码块或者方法,编译器会自动加上monitorenter 与 monitorexit,并且在抛异常的时候也会自动调用monitorexit。

当调用monitorenter时,会获取操作系统的Monitor锁(C++实现)。

比如说Thread1 执行了monitorenter,会先根据要锁住的对象(Java对象),会用这个Java对象(锁对象)的对象头来记录Monitor锁的地址,从而创建Monitor锁,然后把Monitor锁的地址记录在对象头中。这样Thread就会获取到Monitor锁。

获取锁的过程:
(1)首先会检查Monitor的Owner属性,看这个锁是否有主人,如果Owner为null,说明该锁是空闲的,Thread1就可以成为该Monitor的Owner。
(2)此时Thread2执行monitorenter指令去加锁,根据锁对象的对象头获取Monitor的地址,然后找到Monitor锁,发现Owner并不是空,首先会做几次自旋的尝试,如果自旋期间Thread1释放锁正好Thread2获取锁,如果自旋期间Thread1未释放锁,Thread2会存放到Monitor的EntryList队列进行阻塞等待,并且Thread2的状态也会从运行状态变成阻塞状态
(3)当Thread1执行了monitorexit之后,会把Owner释放掉,唤醒EntryList中阻塞的线程,阻塞的Thread2会拿到Monitor。

在这里插入图片描述

3、Synchronized内存屏障

在这里插入图片描述
注意!在Synchronized同步代码块里面的代码,是会发生重排序的。

4、Synchronized正确使用

public class TestSynchronized {
    /**
     * 案例1: 未正确使用 synchronized 情况分析
     */
    @JCStressTest
    @Outcome(id = {"0, 0", "1, 1"}, expect = Expect.ACCEPTABLE, desc = "ACCEPTABLE")
    @Outcome(id = "0, 1", expect = Expect.ACCEPTABLE_INTERESTING, desc = "INTERESTING")
    @State
    public static class Case1 {
        int X = 0;
        int Y = 0;

        final Object LOCK = new Object();

        @Actor
        public void actor1() {
            X = 1;
            synchronized (LOCK) {
                Y = 1;
            }
        }

        @Actor
        public void actor2(II_Result r) {
            synchronized (LOCK) {
                r.r1 = Y;
            }
            r.r2 = X;
        }
    }

    /**
     * 案例2: 未正确使用 synchronized 情况分析
     */
    @JCStressTest
    @Outcome(id = "0, 0", expect = Expect.ACCEPTABLE, desc = "ACCEPTABLE")
    @Outcome(id = "1, 1", expect = Expect.ACCEPTABLE, desc = "ACCEPTABLE")
    @Outcome(expect = Expect.ACCEPTABLE_INTERESTING, desc = "ACCEPTABLE_INTERESTING")
    @State
    public static class Case2 {
        int A;
        int B;

        @Actor
        public void t1() {
            final Object lock = new Object();
            synchronized (lock) {
                A = 1;
                B = 1;
            }
        }

        @Actor
        public void t2(II_Result r) {
            final Object lock = new Object();
            synchronized (lock) {
                r.r1 = A;
                r.r2 = B;
            }
        }
    }

}

5、Synchronized的优化

synchronized 的实现原理以及锁升级详解

重量级锁:当有竞争时,仍会向系统申请 Monitor 互斥锁。

轻量级锁:如果线程加锁、解锁时间上刚好是错开的,这时候就可以使用轻量级锁,只是使用 cas 尝试将对象头替换为该线程的锁记录地址,如果 cas 失败,会锁重入或触发重量级锁升级。

偏向锁(默认):
打个比方,轻量级锁就好比用课本占座,线程每次占座前还得比较一下,课本是不是自己的(cas),频繁 cas 性能也会受到影响;
而偏向锁就好比座位上已经刻好了线程的名字,线程【专用】这个座位,比 cas 更为轻量;
但是一旦其他线程访问偏向对象,那么比较麻烦,需要把座位上的名字擦去,这称之为偏向锁撤销,锁也升级为轻量级锁;
偏向锁撤销也属于昂贵的操作,怎么减少呢,JVM 会记录这一类对象被撤销的次数,如果超过了 20 这个阈值,下次新线程访问偏向对象时,就不用撤销了,而是刻上新线程的名字,这称为重偏向;
如果撤销次数进一步增加,超过 40 这个阈值,JVM 会认为这一类对象不适合采用偏向锁,会对它们禁用偏向锁,下次新建对象会直接加轻量级锁。

6、无锁 vs 有锁

synchronized 更为重量,申请锁、锁重入都要发起系统调用,频繁调用性能会受影响;
synchronized 如果无法获取锁时,线程会陷入阻塞,引起的线程上下文切换成本高;
虽然做了一系列优化,但轻量级锁、偏向锁都是针对无数据竞争场景的;
如果数据的原子操作时间较长,仍应该让线程阻塞,无锁适合的是短频快的共享数据修改操作主要用于计数器、停止标记、或是阻塞前的有限尝试。

五、VarHandle

VarHandle:Java9中保证变量读写可见性、有序性、原子性利器

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/794537.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

fSGAT批量候选基因关联分析丨快速单基因关联分析

候选基因如何分析? 通常情况下关联分析会得到一大堆候选基因,总不可能每个都有用,因此需要对候选基因进行深一步分析,本篇笔记分享一下群体遗传学研究中GWAS候选位点与候选基因的筛选思路。主要的方式包括单基因关联分析、连锁程度…

Appium+python自动化(二十三)- Monkeyrunner与Monkey傻傻的分不清楚(超详解)

monkeyrunner简介 1.monkeyrunner工具使用Jython,这是一种使用Java编程语言的Python实现。Jython允许monkeyrunner API与Android框架轻松交互。使用Jython,您可以使用Python语法来访问API的常量,类和方法。MonkeyRunner工具是使用Jython(使用…

【VCS】(7)Fast Gate-level Verification

Fast Gate-level Verification VCS中SDF反标(Back-Annotation)Lab 门级网表的后仿真DC综合RTL级仿真波形后仿真 网表级的仿真可以验证综合后得到的门级网表和RTL代码是否一致。也可以验证,在加速时序信息(SDF)之后,设计的功能是否…

基于POX、JBX交叉的遗传算法求解车间调度

对于流水车间调度问题,n个工件在m台设备上加工,已知每个工件每个工序使用的机器和每个工件每个工序所用时间,通过决策每个机器上工件的加工顺序和每个工序的开始时间,使完成所有工序所用时间(makespan)最小。具有下列约束&#xf…

58寸透明屏的画质怎么样?

58寸透明屏是一种新型的显示屏技术,它具有透明度高、色彩鲜艳、清晰度高等特点,可以广泛应用于商业展示、户外广告、智能家居等领域。 首先,58寸透明屏的透明度高。 透明屏采用了先进的光学技术,使得屏幕在显示图像的同时&#x…

ubuntu 18.04 磁盘太满无法进入系统

安装了一个压缩包,装了一半提示磁盘空间少导致安装失败。我也没在意,退出虚拟机打算扩展硬盘。等我在虚拟机设置中完成扩展操作,准备进入虚拟机内部进行操作时,发现登录不进去了 shift 登入GUN GRUB设置项的问题 网上都是在开机…

Rust vs Go:常用语法对比(十三)

题图来自 Go vs. Rust: The Ultimate Performance Battle 241. Yield priority to other threads Explicitly decrease the priority of the current process, so that other execution threads have a better chance to execute now. Then resume normal execution and call f…

在centos 7系统docker上构建mysql 5.7

一、VM上已经安装centos 7.9,且已完成docker的构建 二、安装mysql5.7 安装镜像:[rootlocalhost lll]# docker pull mysql:5.7 查看镜像[rootlocalhost lll]# docker images 根据镜像id构建mysql容器,且分配端口号[rootlocalhost lll]# dock…

勘探开发人工智能应用:断层识别

1 断层识别 断层是地下岩层在受到挤压或拉伸力作用下,因脆性变形而形成的地层错断,是一种重要的地质构造特征。断层检测和解释是从地震剖面中认识岩层结构和储层特性的重要步骤。 1.1 数据描述 合成地震数据: 每一个合成地震数据都是由地质模型的反射系数与雷克子波进行褶…

chrome插件推荐

chrome插件推荐 chrome的一些插件, 真的能很大程度上提升我们的工作效率。而且chrome的插件极其丰富, 基本你想要的功能,都能找到对应的插件,接下来给大家推荐几个我自己在用的。 插件 1、Momentum 新标签页 简介: 超漂亮的新标签页面。每日更新精彩背景壁纸图片&…

124.【SpringBoot 源码刨析C】

SpringBoot源码刨析C (三)、SpringBoot核心功能2.Web4.数据响应与内容协商(1).响应JSON(1.1)jackson.jarResponseBody(1.1.1)、返回值解析器(1.1.2)、返回值解析器原理 (1.2).SpringMVC到底支持哪些返回值(…

一遍过JavaSE基础知识

文章目录 前言安装Java Development Kit (JDK)安装jdk配置开发环境验证是否安装配置成功 编写第一个Java程序hello world运行Java程序的流程 数据类型和变量数据类型变量 程序逻辑控制条件语句循环语句跳转语句 数组声明和创建数组访问数组元素数组长度遍历数组多维数组 面向对…

OpenAI大模型生态与ChatGLM ||学习大模型我们需要掌握些什么?

首先我们来看OpenAI系列模型: OpenAI 并不只有一个模型,而是提供了涵盖文本、码、对话、语音、图像领域的一系列模型。 语言类大模型 其中语言类大模型包括: GPT-3、GPT-3.5、GPT-4系列模型。 并且,OpenAI在训练GPT-3的同时训练了参数不同、…

Disulfo ICG Amine,二磺酸吲哚菁绿氨基,应用于多种生物大分子以及药物的检测

资料编辑|陕西新研博美生物科技有限公司小编MISSwu​ Disulfo-ICG-Amine试剂 | 基础知识概述(部分): 中文名称:二磺酸吲哚菁绿氨基 英文名称:Disulfo-ICG-Amine,Disulfo ICG NH2 CAS号:N/A 分子式&#xf…

【Gray Hat Python】构建自己的windows调试器

环境准备 windows10 64bit python3.7 64bit 打开可执行文件,创建进程 定义变量 以下代码用 ctypes 定义了调用 windows API 需要的结构 my_debugger_define.py import ctypesWORD ctypes.c_ushort DWORD ctypes.c_ulong LPBYTE ctypes.POINTER(ctypes.c_uby…

微软5年敏捷转型策略:成功的16个关键

许多管理者怀疑规模化敏捷组织是否可行。微软成功地实现了为期五年的大规模敏捷转型表明,答案是肯定的。微软已不是一艘巨型军舰,而更像是同步行进的快艇组成的舰队:数百个团队中以协调的方式进行敏捷和Scrum。依赖关系如何处理?团…

常用自动化测试工具有哪些?

1、Appium AppUI自动化测试 Appium 是一个移动端自动化测试开源工具,支持iOS 和Android 平台,支持Python、Java 等语言,即同一套Java 或Python 脚本可以同时运行在iOS 和Android平台,Appium 是一个C/S 架构,核心是一…

Java Swing(C/S模式)特效雨滴系统界面

调节不同参数,生成不同特效: ------------------界面截图--------------------- package org.jd.data.netty.big.window.chat.frame.ui.controller.center; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.*;/*** 设计模式: 单例模式* * 自定义线…

Docker 数据管理及网络通信 Dockerfile

一、Docker 的数据管理 管理 Docker 容器中数据主要有两种方式:数据卷(Data Volumes)和数据卷容器(DataVolumes Containers)。 1、数据卷 数据卷是一个供容器使用的特殊目录,位于容器中。可将宿主机的目…

华为OD机试真题 Java 实现【阿里巴巴找黄金宝箱(II)】【2023 B卷 100分】,附详细解题思路

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路1、题目关键点:2、大白话的意思就是3、比如4、思路这不就来了 五、Java算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2023B卷题库疯狂收录中,刷题点这里 专栏导读 本专栏收录于…