初識指令重排序,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);
}
上面這段代碼可能會被重排序:如下
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 循環裡加個方法
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()注釋掉,如下
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鎖的對象沒有資源競争的情況下存在的,不會一直調用作業系統函數實作(第一次會調用),而重量級鎖每次都會調用
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