synchronized底层原理

synchronized是Java中的一个关键字,它允许多个线程同时访问共享资源,避免多线程编程中的竞争问题和死锁问题。

一、synchronized特性

  • 原子性

    指一个操作或多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就不执行。

    synchronized和volatile特性上最大的区别就是原子性,volatile不具备原子性。

  • 可见性

    指多个线程访问同一个资源时,该资源的状态、值信息等对于其他线程都是可见的。

  • 有序性

    指程序执行的顺序按照代码的先后顺序执行。

  • 可重入性

二、synchronized的用法

2.1 修饰普通同步方法(实例方法)

锁的是当前实例对象

public class Text implements Runnable {
    //共享资源
    static int i =0;

    public synchronized void add(){
        i++;
    }
    @Override
    public void run(){
        for (int j =0 ; j<10000;j++){
            add();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Text text = new Text();
        Thread t1 = new Thread(text);
        Thread t2 = new Thread(text);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
}

2.2 静态同步方法

锁的是当前类的class对象

public class Text implements Runnable {
    //共享资源
    static int i =0;

    public static synchronized void add(){
        i++;
    }
    @Override
    public void run(){
        for (int j =0 ; j<10000;j++){
            add();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Text text = new Text();
        Thread t1 = new Thread(text);
        Thread t2 = new Thread(text);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
}

2.3 同步方法块

锁的是括号里面的对象

public class Text implements Runnable {
    //共享资源
    static int i = 0;

    @Override
    public void run() {
        synchronized (this) {
            for (int j = 0; j < 10000; j++) {
                i++;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Text text = new Text();
        Thread t1 = new Thread(text);
        Thread t2 = new Thread(text);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
}

三、synchronized的底层实现

synchronized的底层实现是完全依赖JVM虚拟机的

在这里插入图片描述

JVM中,对象在内存中分为三块区域

  • 对象头
    • Mark Word(标记字段):用于存储对象自身运行时数据,如哈希码、GC分代年龄等。
    • Klass Point(类型指针):对象指向它的类元数据指针,通过这个指针来缺点给这个对象是哪个类的实例。
  • 实例数据:这部分主要是存放类的数据信息,父类的信息。
  • 对齐填充:由于虚拟机要求对象起始地址必须是8字节的整数倍,填充数据不是必须存在的,仅仅是为了字节对齐。

3.1 重量级锁的底部实现原理:Monitor

在JDK1.6之前,synchronized只能实现重量级锁,Java虚拟机是基于Monitor对象来实现重量级锁的。

在Hotspot虚拟机中,Monitor是由ObjectMonitor实现的,源码时C++语言实现的,想深入了解的可以下载Hotspot的源码,我们在这里只是简单介绍一下数据结构

在这里插入图片描述

_owner、_WaitSet和_EntryList 字段比较重要

在这里插入图片描述

从上图可以总结获取Monitor和释放Monitor的流程如下:

  1. 当多个线程同时访问同步代码块时,首先进入EntryList中,然后通过CAS的方式尝试将Monitor中的owner字段设置为当前线程,同时count加1,若发现之前的owner的值就是指向当前线程的,recursions也需要加1.如果CAS尝试获取锁失败,则进入到EntryList中。
  2. 当获取锁的线程调用wait()方法,则会将owner设置为null,同时count减1,recursions减1,当前线程加入到WaitSet中,等待被唤醒。
  3. 当前线程执行完同步代码块时,则会释放锁,count减1,recursions减1.当recursions的值为0时,说明线程已经释放了锁。

3.2 synchronized作用于同步代码块的实现原理

为了方便查看程序字节码执行指令,可以现在IDEA中安装一个jclasslib Bytecode viewer插件

public void run() {
    synchronized (this) {
        for (int j = 0; j < 10000; j++) {
            i++;
        }
    }
}

查看的字节码指令如下:

aload_0
dup
astore_1
monitorenter			//进入同步代码块的指令
iconst_0
istore_2
iload_2
sipush 10000
if_icmpge 27 (+17)
getstatic #2 <Text.i : I>
iconst_1
iadd
putstatic #2 <Text.i : I>
iinc 2 by 1
goto 6 (-18)
aload_1
monitorexit				//结束同步代码块的指令
goto 37 (+8)
astore_3
aload_1
monitorexit				//遇到异常时执行的指令
aload_3
athrow
return

从上面的字节码文件可以看到同步代码块的实现是由monitorentermonitorexit指令完成的,monitorenter指令所在的位置是同步代码块开始的位置,第一个monitorexit指令是用于正常结束同步代码块的指令,第二个monitorexit指令是用于异常结束时所执行的释放Monitor指令。

3.3 synchronized作用于同步方法原理

public synchronized void add(){
    i++;
}

查看的字节码指令如下:

0 getstatic #2 <Text.i : I>
3 iconst_1
4 iadd
5 putstatic #2 <Text.i : I>
8 return

我们发现没有monitorentermonitorexit 这两个指令了,而在查看该方法的class文件的结构信息时发现了Access flags后边的synchronized标识,该标识表明了该方法是一个同步方法。

Java虚拟机可以通过表示来辨别一个方法是否为同步方法。
在这里插入图片描述


总结

  • Java虚拟机是通过进入和退出Monitor对象来实现代码块同步和方法同步的,代码块同步使用的是monitorentermonitorexit 指令实现的,而方法同步是通过Access flags后面的标识来确定该方法是否为同步方法。

3.4 JDK1.6为什么要对synchronized进行优化?

因为Java虚拟机是通过进入和退出Monitor对象来实现代码块同步和方法同步的,而Monitor是依靠底层操作系统的Mutex Lock来实现的,操作系统实现线程之间的切换需要从用户态转到内核态,这个切换成本比较高,对性能影响较大。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值