鎖規則:解鎖必然發生在隨後的加鎖以前
)Synchronized
和 Lock
線程A釋放鎖後,會將共享變動操做刷新到主內存中
線程B獲取鎖時,JMM會將該線程的本地內存置爲無效,被監視器保護的臨界區代碼必須從主內存中讀取共享變量
補充: 使用同步代碼塊的好處在於其餘線程仍能夠訪問非synchronized(this)的同步代碼塊
/**
* 先定義一個測試模板類
* 這裏補充一個知識點:Thread.sleep(long)不會釋放鎖
* 讀者可參見筆者的`併發番@Thread一文通`
*/
public class SynchronizedDemo {
public static synchronized void staticMethod(){
System.out.println(Thread.currentThread().getName() + "訪問了靜態同步方法staticMethod");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "結束訪問靜態同步方法staticMethod");
}
public static void staticMethod2(){
System.out.println(Thread.currentThread().getName() + "訪問了靜態同步方法staticMethod2");
synchronized (SynchronizedDemo.class){
System.out.println(Thread.currentThread().getName() + "在staticMethod2方法中獲取了SynchronizedDemo.class");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void synMethod(){
System.out.println(Thread.currentThread().getName() + "訪問了同步方法synMethod");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "結束訪問同步方法synMethod");
}
public synchronized void synMethod2(){
System.out.println(Thread.currentThread().getName() + "訪問了同步方法synMethod2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "結束訪問同步方法synMethod2");
}
public void method(){
System.out.println(Thread.currentThread().getName() + "訪問了普通方法method");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "結束訪問普通方法method");
}
private Object lock = new Object();
public void chunkMethod(){
System.out.println(Thread.currentThread().getName() + "訪問了chunkMethod方法");
synchronized (lock){
System.out.println(Thread.currentThread().getName() + "在chunkMethod方法中獲取了lock");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void chunkMethod2(){
System.out.println(Thread.currentThread().getName() + "訪問了chunkMethod2方法");
synchronized (lock){
System.out.println(Thread.currentThread().getName() + "在chunkMethod2方法中獲取了lock");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void chunkMethod3(){
System.out.println(Thread.currentThread().getName() + "訪問了chunkMethod3方法");
//同步代碼塊
synchronized (this){
System.out.println(Thread.currentThread().getName() + "在chunkMethod3方法中獲取了this");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void stringMethod(String lock){
synchronized (lock){
while (true){
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
複製代碼
當一個線程進入同步方法時,其餘線程能夠正常訪問其餘非同步方法
public static void main(String[] args) {
SynchronizedDemo synDemo = new SynchronizedDemo();
Thread thread1 = new Thread(() -> {
//調用普通方法
synDemo.method();
});
Thread thread2 = new Thread(() -> {
//調用同步方法
synDemo.synMethod();
});
thread1.start();
thread2.start();
}
---------------------
//輸出:
Thread-1訪問了同步方法synMethod
Thread-0訪問了普通方法method
Thread-0結束訪問普通方法method
Thread-1結束訪問同步方法synMethod
//分析:經過結果可知,普通方法和同步方法是非阻塞執行的
複製代碼
當一個線程執行同步方法時,其餘線程不能訪問任何同步方法
public static void main(String[] args) {
SynchronizedDemo synDemo = new SynchronizedDemo();
Thread thread1 = new Thread(() -> {
synDemo.synMethod();
synDemo.synMethod2();
});
Thread thread2 = new Thread(() -> {
synDemo.synMethod2();
synDemo.synMethod();
});
thread1.start();
thread2.start();
}
---------------------
//輸出:
Thread-0訪問了同步方法synMethod
Thread-0結束訪問同步方法synMethod
Thread-0訪問了同步方法synMethod2
Thread-0結束訪問同步方法synMethod2
Thread-1訪問了同步方法synMethod2
Thread-1結束訪問同步方法synMethod2
Thread-1訪問了同步方法synMethod
Thread-1結束訪問同步方法synMethod
//分析:經過結果可知,任務的執行是阻塞的,顯然Thread-1必須等待Thread-0執行完畢以後才能繼續執行
複製代碼
當同步代碼塊都是同一個鎖時,方法能夠被全部線程訪問,但同一個鎖的同步代碼塊同一時刻只能被一個線程訪問
public static void main(String[] args) {
SynchronizedDemo synDemo = new SynchronizedDemo();
Thread thread1 = new Thread(() -> {
//調用同步塊方法
synDemo.chunkMethod();
synDemo.chunkMethod2();
});
Thread thread2 = new Thread(() -> {
//調用同步塊方法
synDemo.chunkMethod();
synDemo.synMethod2();
});
thread1.start();
thread2.start();
}
---------------------
//輸出:
Thread-0訪問了chunkMethod方法
Thread-1訪問了chunkMethod方法
Thread-0在chunkMethod方法中獲取了lock
...停頓等待...
Thread-1在chunkMethod方法中獲取了lock
...停頓等待...
Thread-0訪問了chunkMethod2方法
Thread-0在chunkMethod2方法中獲取了lock
...停頓等待...
Thread-1訪問了chunkMethod2方法
Thread-1在chunkMethod2方法中獲取了lock
//分析可知:
//1.對比18行和19行可知,即便普通方法有同步代碼塊,但方法的訪問是非阻塞的,任何線程均可以自由進入
//2.對比20行、22行以及25行和27行可知,對於同一個鎖的同步代碼塊的訪問必定是阻塞的
複製代碼
public static void main(String[] args) {
SynchronizedDemo synDemo = new SynchronizedDemo();
Thread thread1 = new Thread(() -> {
//調用同步塊方法
synDemo.chunkMethod();
synDemo.chunkMethod2();
});
Thread thread2 = new Thread(() -> {
//調用同步塊方法
synDemo.chunkMethod2();
synDemo.chunkMethod();
});
thread1.start();
thread2.start();
}
---------------------
//輸出:
Thread-0訪問了chunkMethod方法
Thread-1訪問了chunkMethod2方法
Thread-0在chunkMethod方法中獲取了lock
...停頓等待...
Thread-0訪問了chunkMethod2方法
Thread-1在chunkMethod2方法中獲取了lock
...停頓等待...
Thread-1訪問了chunkMethod方法
Thread-0在chunkMethod2方法中獲取了lock
...停頓等待...
Thread-1在chunkMethod方法中獲取了lock
//分析可知:
//現象:對比20行、22行和24行、25行可知,雖然是同一個lock對象,但其不一樣代碼塊的訪問是非阻塞的
//緣由:根源在於鎖的釋放和從新競爭,當Thread-0訪問完chunkMethod方法後會先釋放鎖,這時Thread-1就有機會能獲取到鎖從而優先執行,依次類推到24行、25行時,Thread-0又從新獲取到鎖優先執行了
//注意:但有一點是必須的,對於同一個鎖的同步代碼塊的訪問必定是阻塞的
//補充:同步方法之全部會被所有阻塞,是由於synDemo對象一直被線程在內部把持住就沒釋放過,論把持住的重要性!
複製代碼
3.2.3
原則3.2.2
和3.2.3
原則public static void main(String[] args) {
SynchronizedDemo synDemo = new SynchronizedDemo();
Thread thread1 = new Thread(() -> synDemo.chunkMethod() );
Thread thread2 = new Thread(() -> synDemo.chunkMethod3());
Thread thread3 = new Thread(() -> staticMethod());
Thread thread4 = new Thread(() -> staticMethod2());
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
---------------------
//輸出:
Thread-1訪問了chunkMethod3方法
Thread-1在chunkMethod3方法中獲取了this
Thread-2訪問了靜態同步方法staticMethod
Thread-0訪問了chunkMethod方法
Thread-0在chunkMethod方法中獲取了lock
Thread-3訪問了靜態同步方法staticMethod2
...停頓等待...
Thread-2結束訪問靜態同步方法staticMethod
Thread-3在staticMethod2方法中獲取了SynchronizedDemo.class
//分析可知:
//現象:對比16行、18行和24行、25行可知,雖然是同一個lock對象,但其不一樣代碼塊的訪問是非阻塞的
//緣由:根源在於鎖的釋放和從新競爭,當Thread-0訪問完chunkMethod方法後會先釋放鎖,這時Thread-1就有機會能獲取到鎖從而優先執行,依次類推到24行、25行時,Thread-0又從新獲取到鎖優先執行了
複製代碼
public static void main(String[] args) {
SynchronizedDemo synDemo = new SynchronizedDemo();
Thread thread1 = new Thread(() -> {
synDemo.synMethod();
synDemo.synMethod2();
});
Thread thread2 = new Thread(() -> {
synDemo.synMethod2();
synDemo.synMethod();
});
thread1.start();
thread2.start();
}
---------------------
//輸出:
Thread-0訪問了同步方法synMethod
Thread-0結束訪問同步方法synMethod
Thread-0訪問了同步方法synMethod2
Thread-0結束訪問同步方法synMethod2
Thread-1訪問了同步方法synMethod2
Thread-1結束訪問同步方法synMethod2
Thread-1訪問了同步方法synMethod
Thread-1結束訪問同步方法synMethod
//分析:對比16行和18行可知,在代碼塊中繼續調用了當前實例對象的另一個同步方法,再次請求當前實例鎖時,將被容許,進而執行方法體代碼,這就是重入鎖最直接的體現
複製代碼
public static void main(String[] args) {
SynchronizedDemo synDemo = new SynchronizedDemo();
Thread thread1 = new Thread(() -> synDemo.stringMethod("sally"));
Thread thread2 = new Thread(() -> synDemo.stringMethod("sally"));
thread1.start();
thread2.start();
}
---------------------
//輸出:
Thread-0
Thread-0
Thread-0
Thread-0
...死循環...
//分析:輸出結果永遠都是Thread-0的死循環,也就是說另外一個線程,即Thread-1線程根本不會運行
//緣由:同步塊中的鎖是同一個字面量
複製代碼
public class SynchronizedDemo {
static Integer i = 0; //Integer是final Class
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int j = 0;j<10000;j++){
synchronized (i){
i++;
}
}
}
};
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(i);
}
}
---------------------
//輸出:
14134
//分析:跟預想中的20000不一致,當使用Integer做爲對象鎖時但還有計算操做就會出現併發問題
複製代碼
咱們經過反編譯發現執行i++操做至關於執行了i = Integer.valueOf(i.intValue()+1)緩存
經過查看Integer的valueOf方法實現可知,其每次都new了一個新的Integer對象,鎖變了有木有!!!bash
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i); //每次都new一個新的鎖有木有!!!
}
複製代碼
public static void main(String[] args) {
Object lock = new Object();
Object lock2 = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lock){
System.out.println(Thread.currentThread().getName() + "獲取到lock鎖");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2){
System.out.println(Thread.currentThread().getName() + "獲取到lock2鎖");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock2){
System.out.println(Thread.currentThread().getName() + "獲取到lock2鎖");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock){
System.out.println(Thread.currentThread().getName() + "獲取到lock鎖");
}
}
});
thread1.start();
thread2.start();
}
---------------------
//輸出:
Thread-1獲取到lock2鎖
Thread-0獲取到lock鎖
.....
//分析:線程0得到lock鎖,線程1得到lock2鎖,但以後因爲兩個線程還要獲取對方已持有的鎖,但已持有的鎖都不會被雙方釋放,線程"假死",沒法往下執行,從而造成死循環,即死鎖,以後一直在作無用的死循環,嚴重浪費系統資源
複製代碼
咱們用 jstack 查看一下這個任務的各個線程運行狀況,能夠發現兩個線程都被阻塞 BLOCKED併發
咱們很明顯的發現,Java-level=deadlock,即死鎖,兩個線程相互等待對方的鎖app
Synchronized一文通(1.8版) 由 黃志鵬kira 創做,採用 知識共享 署名-非商業性使用 4.0 國際 許可協議進行許可。