深入了解java synchronize

Synchronize 是什么

synchronized,中文意思为同步,用于多线程资源共享与维护的最常用手段。它通过线程互斥的手段,保存证了资源的原子性。
使用如下:

synchronized (o) {
            // 业务处理
        }

实现的原理

本文要讲的主要是1.6以后的版本。1.6版本以前版本直接使用重量级锁,没什么好讲的。
1.6版本针对synchronize做了使用优化,根据使用的情使用不同的锁。包括:
偏向锁
1:当线程取得锁时, warkword偏向锁位标记为1,并记录使用该对象的线程指针。
2:当线程尝试向 偏向锁对象 取锁时, 锁将升级为轻量级锁。

轻量级锁
1.当线程拿不到锁对象时,线程不会释放,而是继续while循环空转,直到获取到锁为止。
2.轻量级锁的Markword会记录lock Record的指针,lock Record会记录对象自旋的次数,当它达到一定自旋次数之后,jvm会将它升级为重量级锁。

优点:
无需从用户态转向内核态,在锁竞争比较低的情况,线程只需消化几个时钟周期就能获得锁,所以性能很快。
缺点:
线程自旋是需要消耗cpu性能的,在锁竞争激烈的情况,空转的线程数量和自旋的次数会变高,此时会白白浪费cpu时钟周期。

重量级锁
jvm对重量级锁的实现,是需要依赖操作系统底层的,操作系统底层维护了一个锁的队列,当jvm所有重量级锁的申请,都需要在这个锁队列里面进行排队,线程需要从用户态转向内核态,排队过程线程被挂起,无需消耗cpu时钟频率,直到轮到这个线程获取锁时,系统才会唤醒该争用的线程。

优点:
不消耗cpu,特别时对于大量锁的争用时。
缺点:
等待锁的时间长。

synchronize 锁升级过程

如下图所示:

  1. 初始化,无锁。
  2. 有且只有一线程取得锁时,为偏向锁。
  3. 对象已被锁,并有其它线程尝试取锁时,锁升级为轻量级锁。
  4. 锁状态为轻量级锁,并有更多(达到临界值时)的线程尝试去取锁,轻量级锁将升级为重量级锁。

在这里插入图片描述

线程获取锁过程

如下图所示:

  1. 对象无锁时。直接取得锁
  2. 有锁并且是轻量级锁,线程自旋取得锁。
  3. 有锁并且是重量级锁,线程阻塞,等待锁释放再取得锁。
  4. 取得锁后,用完释放。

在这里插入图片描述

锁状态与markword

对象锁的状态是存在markword记录的,如下图所示:

扫描二维码关注公众号,回复: 10716833 查看本文章
  1. 无锁时,锁标志为01,另外存储了其它各种信息(包括偏向锁状态,分代年龄,hashcode)。
  2. 偏向锁时,锁标志为01,另外存储了取得锁的线程。
  3. 轻量锁时,锁标志为00,存储了线程栈Lock Record的指针。
  4. 重量级锁时,锁标志为10,存储了,重量级锁的指针。

在这里插入图片描述

实例

下面将通过一个实例,结合markword一步一步地演示synchronize 锁的升级过程。

测试过程
  1. 引入JOL打印出对象的结构(关键是markword)。
  2. 模拟无锁,单线程锁,2个线程,100个级线。输出对应的markword。

pom.xml 配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>SynchronizeTest</groupId>
    <artifactId>SynchronizeTest</artifactId>
    <version>1.0-SNAPSHOT</version>

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

</project>

代码

import org.openjdk.jol.info.ClassLayout;

public class ApplicationTest {


    static volatile String strMsg = "";
    static volatile String str2Msg = "";

    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(5000);//因为偏向锁在jvm启动4秒后才启动。所以在这里设置等待5秒。
        Object o = new Object();
        //输出线程
        new Thread() {
            @Override
            public void run() {
                while (true) {
                    if (str2Msg.equals(strMsg) == false) {
                        str2Msg = strMsg;
                        System.out.print(str2Msg);
                    }
                }
            }
        }.start();
        
        //*****************根据测试需要解注*************************
        
        //未锁
         strMsg = ClassLayout.parseInstance(o).toPrintable();
         
        //一个线程锁       
         //SynsTest(1, o);

        //两个线程
        // SynsTest(2,o);

        //100个线程
        // SynsTest(100,o);
    }
      /*
    开启多个线程线线程使用
     */

    public static void SynsTest(int num, final Object o) throws InterruptedException {

        //偏向锁
        for (int i = 0; i < num; i++) {
            System.out.println("启开第" + (i + 1) + "线程");
            new Thread() {
                @Override
                public void run() {
 				  synchronized (o) {
                    for (; ; ) {
                        strMsg = ClassLayout.parseInstance(o).toPrintable();
                        try {
                           
                                Thread.sleep(100);
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
        }
    }
}

测试步骤:

第一步:
1、o对象初始化后,直接打印出o对象。解注以下代码

  		//未锁
         strMsg = ClassLayout.parseInstance(o).toPrintable();
        //一个线程锁
        //SynsTest(1, o);
        //两个线程锁
        // SynsTest(2,o);
        //100个线程
        // SynsTest(100,o);

2、运行结果如下图所示: 状态位为01,断定为无锁
在这里插入图片描述
第二步:
1、开启1个线程取锁。解注以下代码

  		//未锁
        //strMsg = ClassLayout.parseInstance(o).toPrintable();
        //一个线程锁
        SynsTest(1, o);
        //两个线程锁
        // SynsTest(2,o);
        //100个线程
        // SynsTest(100,o);

2、结果如下图所示:状态位为01,后面还记录着 对应的线程指针,断定为 偏向锁
在这里插入图片描述
第三步:
1、开启2个线程最锁。解注以下代码

  		//未锁
        //strMsg = ClassLayout.parseInstance(o).toPrintable();
        //一个线程锁
        //SynsTest(1, o);
        //两个线程锁
         SynsTest(2,o);
        //100个线程
        // SynsTest(100,o);

2、如下图所示:状态位为00,断定为轻量级锁
在这里插入图片描述

第四步:
1、开始100个线程取锁。解注以下代码

  		//未锁
        //strMsg = ClassLayout.parseInstance(o).toPrintable();
        //一个线程锁
        //SynsTest(1, o);
        //两个线程锁
        //SynsTest(2,o);
        //100个线程
         SynsTest(100,o);

2、结果如下图所示:状态位为10,断定为重量级锁

在这里插入图片描述
PS
因为偏向锁默认在jvm启动4秒后才启动。所以在这里设置等待5秒。
具体的结果与配置有关。
本机测试用的环境如下,所有配置都是默认的。
在这里插入图片描述
可以通过以下命令打印出配置的参数。
在这里插入图片描述
本人能力有限,如有错误请指出。
参考文献:
周志明. 深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)
马老师视屏。

发布了31 篇原创文章 · 获赞 88 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/richyliu44/article/details/105327885
今日推荐