java之synchronized

2024-07-09 13:52

其实在JDK 1.6之前,Java内置锁还是一个重量级锁,是一个效率比较低的锁,会由jvm用户态切换到操作系统的管理来实现互斥:

管程 ( Monitor)

Monitor,直译为“监视器”,而操作系统领域一般翻译为“管程”,在java领域就是“对象锁”。

管程是指管理共享变量以及对共享变量操作的过程,让它们支持并发。翻译成Java领域的语言,就是管理类的状态变量,让这个类是线程安全的。

​ **synchronized关键字和wait()、notify()、notifyAll()**这三个方法是Java中实现管理技术的组成部分。

Monitor有两种大作用:同步和互斥

wait/notify基于monitor做的,monitor中有owner、entryList、waitSet

java对象与 Monitor之间的关系

​ “每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象加锁(重量级锁)之后,该对象头的 Mark Word 中就被设置指向 Monitor对象 的指针。

Monitor的基本结构

​ Owner字段: 初始时为NULL表示当前没有任何线程拥有该monitor record,当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为NULL

​ EntryQ字段: 关联一个系统互斥锁(semaphore),阻塞所有试图锁住monitor record失败的线程

​ RcThis字段: 表示blocked或waiting在该monitor record上的所有线程的个数

​ Nest字段: 用来实现重入锁的计数

​ HashCode字段: 保存从对象头拷贝过来的HashCode值(可能还包含GC age)

​ Candidate字段: 用来避免不必要的阻塞或等待线程唤醒,因为每一次只有一个线程能够成功拥有锁,如果每次前一个释放锁的线程唤醒所有正在阻塞或等待

的线程,会引起不必要的上下文切换(从阻塞到就绪然后因为竞争锁失败又被阻塞)从而导致性能严重下降;Candidate只有两种可能的值0表示没有需要唤醒的

线程1表示要唤醒一个继任线程来竞争锁。

Monitor与java对象以及线程关联

​ 如果一个java对象被某个线程锁住,则该java对象的Mark Word字段中LockWord指向monitor的起始地址

​ Monitor的Owner字段会存放拥有相关联对象锁的线程id

方法

Monitor的enter方法:获取锁

Monitor的exit方法:释放锁

Monitor的wait方法:为java的Object的wait方法提供支持

Monitor的notify方法:为java的Object的notify方法提供支持

Monitor的notifyAll方法:为java的Object的notifyAll方法提供支持

结构图如下:

image.png

刚开始 Monitor 中 Owner 为 null,当线程-2 执行 synchronized(obj) 就会将 Monitor 的所有者 Owner 置为 线程-2, Monitor中只能有一个 Owner、在 线

程-2 上锁的过程中,如果 线程-3,线程-4,线程-5 也来执行 synchronized(obj),就会进入EntryList,此时线程状态变为BLOCKED状态、Thread-2 执行完同步

代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争是非公平的(synchronized是非公平锁)和WaitSet 中的 线程-0,线程-1是之前获得过锁的线

程,此时的状态是 WAITING 状态,

wait-notify时的分析如下:

image.png

锁升级

​ JDK 1.6之后,为提高锁的获取与释放效率,对synchronized的实现进行了优化,引入了偏向锁和轻量级锁,从此以后Java内置锁的状态就有了4种(无锁、偏

向锁、轻量级锁和重量级锁),并且4种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级,也就是说只能进行锁升级(从低级别到高级别)。

级别由低到高依次为: 无锁一>偏向锁一>轻量级锁一>重量级锁

Mark Word 标记

主要作用是标识出当前对象的线程锁状态,GC状态标识。

image.png

image.png

age表示对象被GC的次数,当该次数到达阈值的时候,对象就会转移到老年代。

并行GC次数,默认15次。

并发GC次数,默认6次。

age标识位,只有4位,最大能表示的数字:15 这也是 -XX:MaxTenuringThreshold 这个参数最大只能设置15的原因。

image.png

采用小工具查看

<dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.9</version>
            <scope>compile</scope>
</dependency>

示例:

User user = new User();
System.out.println(ClassLayout.parseInstance(user).toPrintable());
System.out.println(ClassLayout.parseClass(User.class).toPrintable());

image.png

image.png

在无锁情况下,如图:

image.png

image.png

偏向锁

​ 偏向锁存 jdk中偏向锁存在延迟4秒启动,也就是说在jvm启动后4秒后创建的对象才会开启偏向锁,可以通过jvm参数取消这个延迟时间。 创建的对象状态为

对象处于无锁状态+可偏向状态 偏向锁位(biased_lock)1bit + 锁标志位(lock)2bit = 1 01

​ 在没有线程竞争的条件下,第一个获取锁的线程通过CAS将自己的threadId写入到该对象的mark word中,若后续该线程再次获取锁,需要比较当前线程

threadId和对象mark word中的threadId是否一致,如果一致那么可以直接获取,并且锁对象始终保持对该线程的偏向,也就是说偏向锁不会主动释放。

 Thread.sleep(4100);//jvm启动后延迟4s以后
        User user = new User();
        synchronized (user){
            System.out.println(ClassLayout.parseInstance(user).toPrintable());

image.png

 Thread.sleep(4100);//jvm启动后延迟4s以后
        User user = new User();
        synchronized (user){
            System.out.println(ClassLayout.parseInstance(user).toPrintable());
        }
        System.out.println(ClassLayout.parseInstance(user).toPrintable());
        synchronized (user){
            System.out.println(ClassLayout.parseInstance(user).toPrintable());
        }

image.png


轻量级锁


​ 当两个或以上线程交替获取锁,但并没有在对象上并发的获取锁时,偏向锁升级为轻量级锁。在此阶段,线程采取CAS的自旋方式尝试获取锁,避免阻塞线程

造成的cpu在用户态和内核态间转换的消耗 主线程首先对user对象加锁,首次加锁为101偏向锁 子线程等待主线程释放锁后,对user对象加锁,这时将偏向锁升级

为00轻量级锁 轻量级锁解锁后,user对象无线程竞争,恢复为001无锁态,并且处于不可偏向状态。如果之后有线程再尝试获取user对象的锁,会直接加轻量级

锁,而不是偏向锁。

 public static void main(String[] args) throws InterruptedException {
        Thread.sleep(4100);//jvm启动后延迟4s以后
        User user = new User();
        synchronized (user){
            System.out.println(ClassLayout.parseInstance(user).toPrintable());
        }

        Thread thread=new Thread() {
            public void run() {
                synchronized (user) {
                    System.out.println(ClassLayout.parseInstance(user).toPrintable());
                }
            }
        };
        thread.start();
        thread.join();
        System.out.println(ClassLayout.parseInstance(user).toPrintable());
    }

image.png

重量级锁

​ 两个或以上线程在同一个对象上进行同步时,为了避免无用自旋消耗cpu,轻量级锁会升级成重量级锁。这时mark word中的指针指向的是monitor对象(也

被称为管理或监视器锁)的起始地址

 public static void main(String[] args) throws InterruptedException {
        Thread.sleep(4100);//jvm启动后延迟4s以后
        User user = new User();
        synchronized (user){
            System.out.println(ClassLayout.parseInstance(user).toPrintable());
        }

        Thread thread1=new Thread() {
            public void run() {
                synchronized (user) {
                    try {
                        TimeUnit.SECONDS.sleep(3);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(ClassLayout.parseInstance(user).toPrintable());
                }
            }
        };
        thread1.start();

        Thread thread2=new Thread() {
            public void run() {
                synchronized (user) {
                    try {
                        TimeUnit.SECONDS.sleep(3);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(ClassLayout.parseInstance(user).toPrintable());
                }
            }
        };
        thread2.start();
        thread1.join();
        thread2.join();
        TimeUnit.SECONDS.sleep(3);
        System.out.println(ClassLayout.parseInstance(user).toPrintable());
    }

image.png

相关新闻
热点
视频
投票
查看结果
Tags

站点地图 在线访客: 今日访问量: 昨日访问量: 总访问量:

© 2025 个人网站 版权所有

备案号:苏ICP备2024108837号

苏公网安备32011302322151号