JUC(二)锁

2022-05-09  本文已影响0人  NIIIICO

一、引言

1、volatile能不能保证线程安全?

public class Test {
    public static int i = 1;

    public static void main(String[] args) {
        i++;
    }
}

// 字节码
0 getstatic #2 <com/example/demo/Test.i : I>
3 iconst_1
4 iadd
5 putstatic #2 <com/example/demo/Test.i : I>

JUC(一)JMM内存模型可知,volatile可以保证可见性和有序性,那么使用volatile可以达到线程安全吗?上面代码是i++的字节码,假设有这样一种场景,由于volatile不能保证原子性,就会导致出现线程不安全:

序号 步骤 线程1工作内存 线程2工作内存 主内存 备注
1 线程1执行,执行getstatic #2;
线程2不执行
计算结果0
i = 1
计算结果0
i = 0
i = 1 -
2 线程1执行,执行iconst_1;
线程2不执行
计算结果0
i = 1
计算结果0
i = 0
i = 1 -
3 线程1执行,执行iadd;
线程2不执行
计算结果2
i = 1
计算结果0
i = 0
i = 1 iadd只进行计算,还未进行赋值
4 线程1不执行;
线程2执行,执行getstatic #2
计算结果2
i = 1
计算结果0
i = 1
i = 1 -
5 线程1不执行;
线程2执行,执行iconst_1
计算结果2
i = 1
计算结果0
i = 1
i = 1 -
6 线程1不执行;
线程2执行,执行iadd
计算结果2
i = 1
计算结果2
i = 1
i = 1 -
7 线程1不执行;
线程2执行,执行putstatic #2
计算结果2
i = 1(失效)
计算结果2
i = 2
i = 2 由于volatile的可见性,给i赋值后,
会同步其他线程i已经失效
8 线程1执行,重新读取i;
线程2不执行
计算结果2
i = 2
计算结果2
i = 2
i = 2 线程1重新从主内存中获取i的值
9 线程1执行,执行putstatic #2;
线程2不执行
计算结果2
i = 2
计算结果2
i = 2
i = 2 线程1将计算结果赋值给i,
导致执行了两次i++,结果还是2

2、竞态条件与临界区

二、synchronized

1、非静态方法锁和静态方法锁的区别

public class Test {
    private int i = 1;
    private static int j = 1;

    // 方法锁
    public synchronized void add() {
        i++;
    }

    // 静态方法锁
    public synchronized static void addStatic() {
        j++;
    }
}

上述代码等价于

public class Test {
    private int i = 1;
    private static int j = 1;
    
    public void add() {
        synchronized (this) {
            i++;
        }
    }
    
    public static void addStatic() {
        synchronized (Test.class) {
            j++;
        }
    }
}

2、锁分类

JVM(三)堆与对象可知,对象头中的MarkWord会有位置标识锁状态:

markword
<1> 偏向锁
<2> 轻量锁
Lock Record 结构
<3> 重量锁
Monitor结构
<4> 锁膨胀过程
锁膨胀过程

3、其他

<1> 为什么要有偏向锁、轻量锁?
<2> 重入
synchronized (this){
    synchronized (this){
        
    }
}
<3> 公平锁、非公平锁
上一篇 下一篇

猜你喜欢

热点阅读