文章目录

  • 前言
  • 一、实现原理
  • 二、代码演示
  • 三、原理分析
  • 总结



前言

synchronized 是 Java中重量级别的锁,学习多线程并发的同学肯定都不陌生,那么它是怎么确保多线程并发操作的原子性呢?

这篇文章带你深入了解其中的原理。


一、实现原理

保证同一时间只有一个线程操作共享变量,synchronized保证只有一个线程能拿到锁,能够进入同步代码块执行。

二、代码演示

需求:对一个共享变量num进行+1操作,开启5个线程同时操作,每个线程只能加1000,结果要求加到5000。

  • 代码如下:
package com.study.syn;

import java.util.ArrayList;
import java.util.List;

/**
 * synchronized 原子性原理分析
 */
public class Automicity {

    /**
     * 全局静态变量(也是共享变量)
     */
    private static int num = 0;

    /**
     * 全局锁
     */
    private static Object obj = new Object();

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

        // 对num进行1000次的++操作
        Runnable increment = () -> {
            for (int i = 0; i < 1000; i++) {
                synchronized (obj) {
                    num++;
                }
            }
        };

        // 开启多线程
        List<Thread> list = new ArrayList<>();

        // 开启5个线程
        for (int i = 0; i < 5; i++) {
            Thread t = new Thread(increment);
            t.start();
            list.add(t);
        }

        for(Thread t : list) {
            t.join();
        }

        System.out.println("num:" + num);
    }

}
  • 我们可以先试试不在num++的部分加锁,如图所示部分:
  • 让我们多执行几次来看看结果:

  • 可以看到,我们执行了3次,只有一次结果是符合我们的预期的。
  • 我们再把代码还原,看看加了synchronized锁之后会怎样:
  • 让我们一起看看执行结果:

  • 可以看到加锁后三次结果都符合我们的预期。

三、原理分析

  • 想要了解其中的原理,我们需要查看class文件的汇编的结果
  • 找到class文件,执行如下命令:
javap -p -v xxx.class

java如何保证多线程有序执行 java多线程怎么保证id唯一_代码块

  • 找到num++ 那段代码:
  • 我们再把同步锁synchronized去掉,再执行一次汇编,看看有什么不同:
  • 从以上可以对比出,加了synchronized之后多出了两个行代码,分别是:
  • 代码块开始部分:monitorenter
  • 代码块结束部分:monitorexit
  • 关于monitor的实现大家可以看看hotspot的源码,是C++写的(OpenJDK8源码下载地址)
  • monitro中有当前拥有锁的线程信息:
  • 也就是说只有这个_owner线程具有执行monitor代码块的权限,从而确保了共享变量同一时间只有一个线程在操作,从而保证了原子性。

总结

Java中synchronized通过控制同一时间可进入代码块的线程有且只能有一个,来保证其原子性。