• 通过修改Linux源码证明有偏向锁的存在


    通过博客《基于JNI手动模拟Java线程》,我们知道Java线程的创建方式,

    本质:就是调用操作系统底层的线程创建函数 , Linux中是pthread_create函数

    那么线程加锁在操作系统又是调用什么函数呢?

    查了一下是调用操作系统中的pthread_mutex_lock函数

    回到我们今天的主题:

    Java对synchronized关键字进行了优化,引入的偏向锁(当线程没有竞争的时候,偏向锁只会加锁一次,后面再去调用该方法的时候,不会再去操作系统调用对应的操作的系统的加锁的函数)

    那我们该如何证明呢?提供了两个思路,不知道是否可行,只能先尝试。

    1. 思路一:在我上次编译好的JDK源码中,找到对应的pthread_mutex_lock函数,为其加一个断点,然后书写对应的Java代码(只启动一个线程),进行调试,看这个断点会进入几次,如果是一次就证明是偏向锁,如果不是一次,证明不是偏向锁。
    2. 思路二:修改Linux源码对应的函数,为其加上打印语句,打印当前线程的ID,然后在Java中打印对应的Java的线程ID,通过比较如果两个ID是一样的,证明加锁了,找到整个成对个数,如果是一次,就只是加锁了一次。

    思路一的尝试:

    Linux下新建SyncDeom.java,书写一下的Java代码,进行调试,至于怎么在OpenJDK源码中调试,可以作者的博客《Ubuntu18.04下编译JDK12》书写的Java代码如下:

    public class SyncDemo {
        Object o = new Object();
    
        public static void main(String[] args) {
            SyncDemo syncDemo = new SyncDemo();
            syncDemo.start();
        }
    
        public void start() {
            Thread thread = new Thread() {
                public void run() {
                    while (true) {
                        sync();
                    }
                }
            };
            Thread thread2 = new Thread() {
                @Override
                public void run() {
                    while (true) {
                        sync();
                    }
                }
            };
            thread.setName("t1");
            thread2.setName("t2");
            thread.start();
        }
    
        public void sync() {
            synchronized (o) {
                System.out.println("haha");
            }
        }
    }
    
    

    在这里插入图片描述

    编译上面代码,然后在OpenJDK中进行调试,按照思路一进行操作,如果断点只进入一次,证明是偏向锁。

    在这里插入图片描述

    打开Clion IDE找到对应方法的断点,我终究还是太年轻了,以为像找创建线程函数一样的好找,但是打开一看,完全不知道是调用那个的方法,具体图如下:

    在这里插入图片描述

    思路一完全是行不通的,我还忽略了一个问题,就是java启动的时候,还有很多其他的线程需要同步,(GC线程也需要同步)故会调用操作系统的pthread_mutex_lock的方法,所以这个思路是完全不行,就算我不创建自己的线程,这个方法也不止调用一次。我真的有点蠢!!!

    思路二:我们要在pthread_mutex_lock加一句打印的话,就必须要编译Linux的源码,找到对应的函数加上对应的打印语句,然后下面我会详细的说明编译Linux的源码过程。

    笔者的编译环境如下,请尽量保持和笔者的环境一致,不然可能编译通过不了。

    操作系统:centos7
    Linux源码:Glibc2.19
    

    在编译Linux源码之前要先确保新装的Linux中安装Java环境,运行java和javac命令,看是否可用?

    在这里插入图片描述

    可以看到我们新装的系统javac命令不可用,于是要执行如下的命令进行java的搜索和安装

    yum update
    yum search java | grep -i --color jdk
    
    

    在这里插入图片描述

    选择Jdk1.8安装,具体命令如下:

    yum install -y java-1.8.0-openjdk.x86_64 java-1.8.0-openjdk-devel.x86_64
    
    

    在这里插入图片描述

    再次验证验证javac和java命令是否可用?

    在这里插入图片描述

    在这里插入图片描述

    发现都是可用的状态了,前置的准备工作已经准备好了,开始编译Linux源码。

    我们要先去http://mirror.hust.edu.cn/gnu/glibc/网站下载glibc2.19版本,如下图

    在这里插入图片描述

    编译前我们要准备编译的环境gcc,具体的命令如下:

    yum install -y gcc
    
    

    在这里插入图片描述

    安装过后,运行以下的命令,查看gcc是否安装成功

    gcc -v
    
    

    在这里插入图片描述

    修改glibc的源码,先将下载好的glibc源码解压,具体命令如下:

    tar -zxvf 
    
    

    在这里插入图片描述

    解压过后我们要修改pthread_mutex_lock的代码,pthread_mutex_lock的代码在glibc-2.19/nptl/下,具体指令如下:

    cd glibc-2.19/nptl
    vim pthread_mutex_lock.c
    
    

    在这里插入图片描述

    修改代码如下:

    先导入对应的头文件

    在这里插入图片描述

    加入对应的打印语句

    在这里插入图片描述

    接下来就是编译,在编译前,我们先查看当前系统的glibc的版本,具体的指令是:

    ldd --version
    

    在这里插入图片描述

    现在开始编译,编译的指令如下(请在root账户下执行):

    mkdir build //在glibc-2.19目录下新建一个build文件夹cd build //进入build目录下../configure --prefix=/usr --disable-profile --enable-add-ons --with-headers=/usr/include --with-binutils=/usr/bin
    

    在这里插入图片描述

    在这里插入图片描述

    检测完成,执行编译的命令(请在root账户下执行)

    make
    

    在这里插入图片描述

    执行完make命令,再执行如下的命令(请在root账户下执行)

    make install
    

    在这里插入图片描述

    我们再次验证我们系统的glibc的版本,执行下面的命令:

    ldd --version
    

    在这里插入图片描述

    可以看到我们的glibc的版本变成了2.19,然后线程的编号也打印了。一切都准备好了,开始验证我们的猜想。重新编写刚才的SyncDemo类,具体代码如下:

    public class SyncDemo {
        Object o = new Object();
    
        static {
            System.loadLibrary("SyncDemoNative");
        }
    
        public static void main(String[] args) {
            System.out.println("---------main thread begin---------------------");
            SyncDemo syncDemo = new SyncDemo();
            syncDemo.start();
        }
    
        public void start() {
            Thread thread = new Thread() {
                public void run() {
                    while (true) {
                        sync();
                    }
                }
            };
            Thread thread2 = new Thread() {
                @Override
                public void run() {
                    while (true) {
                        sync();
                    }
                }
            };
            thread.setName("t1");
            thread2.setName("t2");
            thread.start();
            // thread2.start();
        }
    
        //书写一个本地的方法来获取对应的线程ID,因为java方法中,获取的线程ID是Java虚拟机分配的,并不是操作系统的线程ID
        public native void tid();
    
        public void sync() {
            synchronized (o) {
                tid();
            }
        }
    }
    
    

    然后可以用JNI的技术书写本地的方法,具体过程可以参考我的博客《基于JNI手动模拟Java线程》书写的c代码如下所示:

    #include<pthread.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include"SyncDemo.h"
    JNIEXPORT void JNICALL Java_SyncDemo_tid(JNIEnv *env, jobject c1){
        printf("Java Thread Id:%lu---------------
    ",pthread_self());
        usleep(700);
    }
    
    

    然后执行以下的命令

    gcc -fPIC -I /usr/lib/jvm/java‐1.8.0‐openjdk/include -I /usr/lib/jvm/java‐1.8.0‐openjdk/include/linux -shared -o libSyncDemoNative.so SyncDemo.c
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/ys
    
    

    在这里插入图片描述

    接下来,只需要运行java -XX:BiasedLockingStartupDelay=0 SyncDemo即可,这里要关闭偏向锁的延迟

    在这里插入图片描述

    上面可以看到java的主线程启动了。

    在这里插入图片描述

    上面我们可以看到Java的线程打印的线程ID号和操作系统的pthread_mutex_lock函数打印的线程ID号相同只成对出现了一次,后面都没有成对出现过,只出现了一次,证明只加锁了一次,是偏向锁。这时候我们开启两个线程,让其产生竞争,然后看看是不是重量锁,修改原来的代码,将thread2线程启动起来。再次运行,查看结果

    在这里插入图片描述

    上面打印的语句表示是Java的主线程启动了。

    在这里插入图片描述

    从上面的代码可以看到Java的线程打印的线程ID号和操作系统的pthread_mutex_lock函数打印的线程ID号相同成对出现的次数不止一次,故加锁的次数也不止一次,所以加的是重量锁。

  • 相关阅读:
    [java,2019-01-28] 枪手博弈,谁才是最后赢家
    [java,2019-01-25] 图片和二进制互转
    [java,2019-01-15] word转pdf
    [python,2018-06-29] 37%法则及其拓展解决恋爱问题
    [java,2018-06-26] 扑克牌抽牌求和问题
    [python,2018-06-25] 高德纳箭号表示法
    [java,2017-06-12] myEclipse双击无法打开文件
    OpenGL核心技术之法线贴图
    游戏中水的渲染技术系列一
    Unity 3D实现帧同步技术
  • 原文地址:https://www.cnblogs.com/tangliMeiMei/p/15476049.html
Copyright © 2020-2023  润新知