初識指令重排序,Java 中的鎖

初識指令重排序,Java 中的鎖

指令重排序
Java語言規範JVM線程內部維持順序化語義,即只要程序的最終結果與它順序化情況的結果相等,那麼指令的執行順序可以與代碼邏輯順序不一致,這個過程就叫做指令的重排序。
    指令重排序的意義:使指令更加符合CPU的執行特性,最大限度的發揮機器的性能,提高程序的執行效率。
看個demo

public static void main(String[] args) throws InterruptedException {

    int j=0;
    int k=0;
    j++;
    System.out.println(k);
    System.out.println(j);
}

上面這段代碼可能會被重排序:如下

public static void main(String[] args) throws InterruptedException {

    int k=0;
    System.out.println(k);
    int j=0;
    j++;
    System.out.println(j);
}

此時指令的執行順序可以與代碼邏輯順序不一致,但不影響程序的最終結果.

再看個demo

public class ThreadExample2 {

static int i;
public  static boolean runing = true;

public static void main(String[] args) throws InterruptedException {
    traditional();
    Thread.sleep(100);
    runing = false;
}

public static void traditional() {
    Thread thread = new Thread() {
        @Override
        public void run() {
            while (runing){
                i++;//沒有方法,JVM會做指令重排序,激進優化
            }
        }
    };
    thread.start();
}

}

執行下main方法

可以看出該程序一直在跑,不會停止.

此時jvm發現traditional方法內沒有其他方法,JVM會做指令重排序,採取激進優化策略,對我們的代碼進行了重排序

如下:

static int i;

public  static boolean runing = true;

public static void main(String[] args) throws InterruptedException {
    traditional();
    Thread.sleep(100);
    runing = false;
}

public static void traditional() {
    Thread thread = new Thread() {
        boolean temp=runing;//注意這裏,此時while的條件永遠爲true
        @Override
        public void run() {
            while (temp){
                i++;//沒有方法,JVM會做指令重排序,激進優化
            }
        }
    };
    thread.start();
}

因此程序不會停止.

我們稍微改動下代碼,在while 循環里加個方法

static int i;

public  static boolean runing = true;

public static void main(String[] args) throws InterruptedException {
    traditional();
    Thread.sleep(100);
    runing = false;
}

public static void traditional() {
    boolean temp=runing;
    Thread thread = new Thread() {
        @Override
        public void run() {
            while (runing){//
                i++;//沒有方法,JVM會做指令重排序,激進優化
                //有方法,JVM認爲可能存在方法溢出,不做指令重排序,保守優化策略
                aa();
            }
        }
    };
    thread.start();
}

public static void aa(){
    System.out.println("hello");
}

看下結果

可以看出,程序自行停止了,因爲有方法,JVM認爲可能存在方法溢出,不做指令重排序,採取保守優化策略

runing = false;
全局變量runing 改動值以後,被thread線程識別,while 循環裏值變爲false,就自動停止了.

ok,繼續,我們把main方法中的sleep()註釋掉,如下

public static void main(String[] args) throws InterruptedException {

    traditional();
    //Thread.sleep(100);
    runing = false;//會優先執行主線程的代碼
}

public static void traditional() {
    boolean temp=runing;
    Thread thread = new Thread() {
        @Override
        public void run() {
            while (runing){//
                i++;
            }
        }
    };
    thread.start();
}

看下結果:

此時,程序停止了,這是爲什麼呢:

可能是因爲thread 線程和main線程競爭cpu資源的時候,會優先分配給main線程(我不確定,讀者們可以自己思考一下)

Java 中的鎖
synchronized關鍵字
在1.6版本之前,synchronized都是重量級鎖

1.6之後,synchronized被優化,因爲互斥鎖比較笨重,如果線程沒有互斥,那就不需要互斥鎖

重量級鎖
1.當一個線程要訪問一個共享變量時,先用鎖把變量鎖住,然後再操作,操作完了之後再釋放掉鎖,完成

2.當另一個線程也要訪問這個變量時,發現這個變量被鎖住了,無法訪問,它就會一直等待,直到鎖沒了,它再給這個變量上個鎖,然後使用,使用完了釋放鎖,以此進行

3.我們可以這麼理解:重量級鎖是調用操作系統的函數來實現的鎖--mutex--互斥鎖

以linux爲例:

1.互斥變量使用特定的數據類型:pthread_mutex_t結構體,可以認爲這是一個函數
2.可以用pthread_mutex_init進行函數動態的創建 : int pthread_mutex_init(pthread_mutex_t mutex, const pthread_mutexattr_t attr)
3.對鎖的操作主要包括加鎖 pthread_mutex_lock()、解鎖pthread_mutex_unlock()和測試加鎖 pthread_mutex_trylock()三個
3.1 int pthread_mutex_tlock(pthread_mutex_t *mutex) 在寄存器中對變量操作(加/減1)
3.2 int pthread_mutex_unlock(pthread_mutex_t *mutex) 釋放鎖,狀態恢復
3.3 int pthread_mutex_trylock(pthread_mutex_t *mutex)
pthread_mutex_trylock()語義與pthread_mutex_lock()類似,不同的是在鎖已經被佔據時返回EBUSY而不是掛起等待
函數pthread_mutex_trylock會嘗試對互斥量加鎖,如果該互斥量已經被鎖住,函數調用失敗,返回EBUSY,否則加鎖成功返回0,線程不會被阻塞

偏向鎖
偏向鎖是synchronized鎖的對象沒有資源競爭的情況下存在的,不會一直調用操作系統函數實現(第一次會調用),而重量級鎖每次都會調用

看個demo

public class SyncDemo2 {

Object o= new Object();

public static void main(String[] args) {
    System.out.println("pppppppppppppppppppppp");
    SyncDemo2 syncDemo = new SyncDemo2();
    syncDemo.start();
}

public void start() {
    Thread thread = new Thread() {
        public void run() {
            while (true) {
                try {
                    Thread.sleep(500);
                    sync();
                } catch (InterruptedException e) {

                }
            }
        }
    };

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

    thread.setName("t1");
    thread2.setName("t2");
    //兩個線程競爭時,synchronized是重量級鎖,一個線程時,synchronized是偏向鎖
    thread.start();
    thread2.start();
}

//在1.6版本之前,synchronized都是重量級鎖
//1.6之後,synchronized被優化,因爲互斥鎖比較笨重,如果線程沒有互斥,那就不需要互斥鎖
public void sync() {
    synchronized (o) {
        System.out.println(Thread.currentThread().getName());
    }
}

}

代碼很簡單,就是啓動兩個線程,並且調用同一個同步方法,看下結果

可以看到,兩個線程都執行了該同步方法,此時兩個線程競爭,synchronized是重量級鎖

我們把一個線程註釋掉

//兩個線程競爭時,synchronized是重量級鎖,一個線程時,synchronized是偏向鎖

    thread.start();
    //thread2.start();

看下結果:

此時synchronized是偏向鎖

那麼怎麼證明呢:我目前沒那個實力,給個思路.

1.需要編譯並修改linux源碼函數pthread_mutex_lock(),在函數中打印當前線程的pid

2.在同步方法中打印語句"current id"+當前pid(需要自己寫c語言實現),java的Thread.currentThread().getId()不能獲取操作系統級別的pid

3.兩個線程競爭時,執行一次

說明是重量級鎖,因爲每次都調用操作系統的函數pthread_mutex_lock()來實現

4.註釋掉一個線程,再執行一次

說明是偏向鎖,因爲第一次會調用pthread_mutex_lock(),後面就不調用系統函數了.

原文地址https://www.cnblogs.com/lusaisai/p/12731593.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章