java併發筆記之證明 synchronized鎖 是否真實存在

王六六666發表於2019-07-28

警告⚠️:本文耗時很長,先做好心理準備
證明:偏向鎖、輕量級鎖、重量級鎖真實存在
【java併發筆記之java執行緒模型】連結: https://www.cnblogs.com/yuhangwang/p/11256476.html這篇文章可知:每當java執行緒建立的時候相對應的os pthread_create()也會建立一個執行緒,使用synchronized()就必然呼叫os pthread_mutex_lock() 函式
synchronized關鍵字鎖的狀態:無鎖、偏向鎖、輕量級鎖、重量級鎖
此篇文章由證明偏向鎖是否存在入手,眾所周知偏向鎖一定會保證執行緒安全 ,但是實際情況不一定有互斥,偏向鎖是synchronized鎖的物件沒有資源競爭的情況下,不會呼叫os pthread_mutex_lock() 函式; 但是第一次初始化使用鎖的時候確實會呼叫一次pthread_mutex_lock()進行偏向鎖
猜想:偏向鎖一定真實存在
求證方法:
  1.修改Linux原始碼中glibc庫中pthread_mutex_lock.c檔案中的pthread_mutex_lock() 方法,增加輸出當前os id 語句;
  2.java程式碼中使用synchronized關鍵字加鎖,列印出加鎖前執行緒id(此執行緒id會轉化為真實os 執行緒Id),1和2兩者相互比較;
  3.如果呼叫os pthread_mutex_lock() os-id 與 java thread-id 相同: 說明鎖真的存在, 並且只出現過一次相同為偏向鎖
開始求證:
環境搭建:(此處注意linux核心必須與gilbc庫相對應,否則編譯不成功
linux:centos 7  
Gilbc:Gilbc-2.19 官方gilbc連結http://mirror.hust.edu.cn/gnu/glibc/
避免踩坑,個人環境已經上傳百度網盤:請自行下載安裝 百度網盤地址:連結:https://pan.baidu.com/s/1LULbCoxm-ooPnZbGG3tdAQ 密碼:9jwu

檢查環境:
一、首先檢查自己的linux有沒有java環境:
    通過java、和javac兩個命令檢視是否可用
    如果沒有則檢查一下yum當中有哪些JDK可以提供安裝命令:
yum search java | grep -i --color jdk

 如果沒有安裝,  以jdk8為例:

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

二、在檢查inux環境要與gIibc版本是否相對應(不對應會導致編譯失敗)
三、檢查有沒有安裝gcc,用來編譯C程式,沒有就安裝gcc
yum -y install gcc

四、解壓glibc

tar -zxvf glibc-2.19.tar.gz

五、修改glibc的原始碼

修改:pthread_mutex_lock()該方法,加入列印當前os threadId(也就是呼叫該方法時列印)
路徑:{your-dir}/glibc-2.19/nptl/pthread_mutex_lock.c---pthread_mutex_lock()
   pthread_mutex_lock.c檔案下 pthread_mutex_lock() 函式下 第66行修改為:
     printf(stderr,”msg tid=%lu\n",pthread_self);
  (stderr:檔案描述符,pthread_self:當前執行緒id)

修改完之後,以後所有呼叫pthread_mutex_lock()函式都會列印出自己的執行緒id
六、編譯剛才修改完的檔案:
cd glibc-2.19

  編譯完成後要存放的檔案位置:

mkdir out

   編譯(完成後覆蓋系統預設的檔案,請提前備份預設檔案,以防不測

cd out
../configure --prefix=/usr --disable-profile --enable-add-ons --with-headers=/usr/include --with-binutils=/usr/bin

  執行:

make
//make 執行完畢之後再執行
make install

   測試 執行:

java

 只有呼叫了pthread_mutex_lock()會列印出自己的執行緒id:

  

表示此處修改linux原始碼glibc庫下的pthread_mutex_lock()成功;
接下來就該驗證偏向鎖到底是不是真實存在的:
java程式碼:
import java.lang.Thread;

public class ThreadTest {
    Object o= new Object();
    static {
        System.loadLibrary( "TestThreadNative" );
    }
    public static void main(String[] args) {
        //列印出主執行緒
        System.out.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxx");
        ThreadTest example4Start = new ThreadTest();
        example4Start.start();
    }
    public void start(){
        Thread thread = new Thread(){
            @Override
            public void run() {
                while (true){
                    try {
                        sync();
                    } catch (InterruptedException e) {

                    }
                }
            }
        };

        Thread thread2 = new Thread(){
            @Override
            public void run() {
                while (true){
                    try {
                        sync();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        thread.setName("t1");
        thread2.setName("t2");
        
        thread.start();
    }

    //.c 檔案列印出java threaid 對應的os threadid
    public native void tid();

    public  void sync() throws InterruptedException {
        synchronized(o) {
            //java threadid 是jvm給的執行緒id 並不是真是的os 對應的執行緒id
            //System.out.println(Thread.currentThread().getId());

            //獲取java thread 對應的真實的os thread 列印出id
            tid();
        }
    }
}

獲取真實執行緒id(os 執行緒Id)的c檔案:(對應上邊java程式碼的 native void tid())

#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include "Example4Start.h"
JNIEXPORT void JNICALL Java_Example4Start_tid(JNIEnv *env, jobject c1){
    printf("current tid:%lu-----\n",pthread_self());
    usleep(700);
}

然後再走一遍生成.class、.h 、so 然後執行(此方法在【java併發筆記之java執行緒模型】連結: https://www.cnblogs.com/yuhangwang/p/11256476.html中有相對應執行方法,請參考)
執行:
java ThreadTest
顯示: 

  

  

實錘:紅色的證明偏向鎖真實存在,綠色的證明synchronized 1.6之後偏向鎖做了優化(只呼叫了一次os 函式,後面沒有呼叫
同理輕量級鎖及重量級鎖也可以這樣證明出來 

 

相關文章