synchronized锁及其原理

# Java并发机制的底层实现之volatile关键字 提到,volatile修饰的共享变量并不能具有原子性,对一个变量的修改,乃至一段代码的执行,如果想具有原子性的话,需要使用synchronized关键字进行修饰,即对代码块或者方法进行加锁处理,加锁之后,线程只能按照顺序执行这个代码块或者方法。相当于多车道在前方突然变成了单条车道,这时就得一辆辆车通行了,这无疑会导致系统的执行效率降低,但有时也是无奈之举。

下面的代码演示了synchronized关键字的使用:

java 复制代码
public class SynchronizedTestDemo01 {
    static int number = 0;

    synchronized static void addOne() {
        number++;
    }

    public static void main(String[] args) throws InterruptedException {

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    addOne();
                }
            }).start();
        }
        TimeUnit.SECONDS.sleep(5);
        System.out.println(number);
    }
}

这段代码每次输出的都是10000,说明经过synchronized关键字修饰以后,addOne()方法中的代码具有原子性。这里还有另外一个引申出一个问题,这里我的number没有经过volatile关键字修饰,它是怎么做到每一次更新都被其他线程读取到的,是不是synchronized关键字也有volatile关键字的可见性效果?

每次在进入到synchronized关键字修饰的代码块或者方法的时候,都会清空这个线程的CPU缓存,代码块或者方法里面的变量值都要去主内存中获取。每次退出synchronized关键字修饰的代码块或者方法的时候,都会将线程在CPU缓存中的内容刷新到主内存中。所以synchronized关键字确实有volatile关键字的可见性效果。

synchronized关键字是如何实现锁功能的?查看addOne()方法的字节码:

java 复制代码
 static synchronized void addOne();
    descriptor: ()V
    flags: (0x0028) ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #7                  // Field number:I
         3: iconst_1
         4: iadd
         5: putstatic     #7                  // Field number:I
         8: return
      LineNumberTable:
        line 11: 0
        line 12: 8

可以看到 flags 中有一个ACC_SYNCHRONIZED标识,表示这是一个经过synchronized关键字修饰的方法。

如果使用synchronized关键字修饰代码块,如下所示:

java 复制代码
import java.util.concurrent.TimeUnit;
public class SynchronizedTestDemo02 {
    static int number = 0;

    static void addOne() {
        synchronized (SynchronizedTestDemo02.class){
            number++;
        }
    }
    public static void main(String[] args) throws InterruptedException {

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    addOne();
                }
            }).start();
        }
        TimeUnit.SECONDS.sleep(5);
        System.out.println(number);
    }
}

查看addOne()方法的字节码:

java 复制代码
 static void addOne();
    descriptor: ()V
    flags: (0x0008) ACC_STATIC
    Code:
      stack=2, locals=2, args_size=0
         0: ldc           #7                  // class com/example/threadmodule/synchronizedtest/SynchronizedTestDemo02
         2: dup
         3: astore_0
         4: monitorenter
         5: getstatic     #9                  // Field number:I
         8: iconst_1
         9: iadd
        10: putstatic     #9                  // Field number:I
        13: aload_0
        14: monitorexit
        15: goto          23
        18: astore_1
        19: aload_0
        20: monitorexit
        21: aload_1
        22: athrow
        23: return

可以看到字节码的第4行和第14行分别是monitorenter和monitorexit指令,JVM就是靠它两实现的获取锁和释放锁。其实对于ACC_SYNCHRONIZED标识,JVM在执行时,会隐式的调用monitorenter获取锁,当方法退出时或抛出异常时,会隐式的调用monitorexit释放锁。

综上,当JVM遇到monitorenter指令的时候会尝试去获取锁,当遇到monitorexit指令的时候,会从同步代码块中退出,并释放锁。至于多个线程竞争锁的细节,后面将再介绍

相关推荐
A懿轩A26 分钟前
【Maven 构建工具】从零到上手 Maven:安装配置 + IDEA 集成 + 第一个项目(保姆级教程)
java·maven·intellij-idea
野犬寒鸦35 分钟前
从零起步学习并发编程 || 第一章:初步认识进程与线程
java·服务器·后端·学习
我爱娃哈哈39 分钟前
SpringBoot + Flowable + 自定义节点:可视化工作流引擎,支持请假、报销、审批全场景
java·spring boot·后端
XiaoFan0121 小时前
将有向工作流图转为结构树的实现
java·数据结构·决策树
小突突突1 小时前
浅谈Java中的反射
java·开发语言
Anastasiozzzz1 小时前
LeetCode Hot100 295. 数据流的中位数 MedianFinder
java·服务器·前端
我真的是大笨蛋2 小时前
Redo Log详解
java·数据库·sql·mysql·性能优化
索荣荣2 小时前
Java动态代理实战:从原理到精通
java·开发语言
兩尛2 小时前
c++的数组和Java数组的不同
java·开发语言·c++
roman_日积跬步-终至千里2 小时前
【Java并发】多线程/并发问题集
java·开发语言