執行緒八鎖現象的理解
八鎖即鎖的八個問題
一、正常情況下執行緒的執行順序
package demo_02;
// 八鎖問題
// 正常執行緒執行情況
public class Test_01 {
public static void main(String[] args) {
PrintMessage pm = new PrintMessage();
new Thread(()->{pm.printA();},"A").start();
new Thread(()->{pm.printB( );},"B").start();
}
}
class PrintMessage{
public synchronized void printA(){
System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "A");
}
public synchronized void printB(){
System.out.println(Thread.currentThread().getName( )+" 輸出了 => " + "B");
}
}
執行結果:
可以看到這裡是先執行的printA,在執行的printB,可是原因是什麼呢?因為先呼叫的printA方法,再呼叫的printB方法嗎?答案是否定的
二、睡眠A執行緒3秒,執行緒的執行順序
有人可能會覺得是因為先呼叫的printA方法再呼叫的printB方法的問題,因此我們讓printA方法睡三秒
package demo_02;
// 八鎖問題
import java.util.concurrent.TimeUnit;
public class Test_01 {
public static void main(String[] args) {
PrintMessage pm = new PrintMessage();
new Thread(()->{
try {
pm.printA();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{pm.printB();},"B").start();
}
}
class PrintMessage{
public synchronized void printA() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "A");
}
public synchronized void printB(){
System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "B");
}
}
執行結果:
不管執行多少次都是先列印A再列印B,注意這裡並不是因為先呼叫的printA方法,再呼叫printB方法的問題,這樣理解根本沒有理解到關鍵。
這裡出現先列印A再列印B的原因是因為大家沒有理解synchronized鎖,鎖的是什麼?這個地方鎖的是PrintMessage這個類的呼叫者pm物件,所以在A執行緒屌用printA方法時,會對pm物件加鎖,B執行緒要等待A執行緒釋放pm鎖之後才可以獲得pm物件。
三、普通方法和synchronized方法執行問題
package demo_02;
// 八鎖問題
import java.util.concurrent.TimeUnit;
public class Test_01 {
public static void main(String[] args) {
PrintMessage pm = new PrintMessage();
new Thread(()->{
try {
pm.printA();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{pm.printC();},"C").start();
}
}
class PrintMessage{
public synchronized void printA() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "A");
}
public void printC(){
System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "C");
}
}
執行結果:
這裡為了讓結果看起來更清晰,我將A執行緒睡了三秒。這裡先列印C的原因是在A執行緒拿到了pm物件的鎖的時候,C執行緒可以直接對pm物件進行操作,因為printC方法並不是同步方法,不需要進行同步操作,A執行緒有沒有鎖對C執行緒呼叫printC方法都沒有影響。
四、兩個執行緒不同的物件執行順序
為了獲取觀察效果,在這裡同樣讓執行緒A睡3秒
package demo_02;
// 八鎖問題
import java.util.concurrent.TimeUnit;
public class Test_01 {
public static void main(String[] args) {
PrintMessage pm1 = new PrintMessage();
PrintMessage pm2 = new PrintMessage();
new Thread(()->{
try {
pm1.printA();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{pm2.printB();},"B").start();
}
}
class PrintMessage{
public synchronized void printA() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "A");
}
public synchronized void printB(){
System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "B");
}
}
執行結果:
這裡雖然是兩個同步方法,但是synchronized鎖的物件不同,因此執行緒A和執行緒B呼叫的printA和printB方法分別是不對物件的方法,兩個執行緒並不衝突,因為A執行緒睡眠的3秒,所以先列印B再列印A。
五、靜態同步方法的執行順序
將同步方法更改為static靜態方法,看一看執行順序有什麼不同。
static修飾的方法會在類第一次載入的時候被執行。
package demo_02;
// 八鎖問題
import java.util.concurrent.TimeUnit;
public class Test_01 {
public static void main(String[] args) {
PrintMessage pm = new PrintMessage();
new Thread(()->{
try {
pm.printA();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{pm.printB();},"B").start();
}
}
class PrintMessage{
public static synchronized void printA() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "A");
}
public static synchronized void printB(){
System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "B");
}
}
執行結果:
這裡雖然和前面的正常情況的輸出結果一樣,但是其內在原理卻不相同。首先我們需要理解一下static關鍵字,static宣告的靜態方法,會在類被載入的時候就執行。可以理解成static方法在物件被new的時候就已經執行,執行後的方法會放在class檔案中(java檔案編譯後產生的class檔案),class又被稱為該類的模板,一個類在執行過程只只會有一個模板這裡synchronized鎖,鎖的物件是一個模板,而不是一個類物件,雖然輸出結果一樣但是鎖的物件並不相同。
可能很多同學還不是太懂於是就有了第六個鎖的問題
六、兩個物件,兩個靜態同步方法
如果之前大家可能還沒有想通,我們建立兩個PrintMessage的物件,分別呼叫列印方法,按道理如果A執行緒睡眠3秒,那B執行緒就會先列印,然後在列印A執行緒的輸出方法,但是如果是靜態同步方法呢?
package demo_02;
// 八鎖問題
import java.util.concurrent.TimeUnit;
public class Test_01 {
public static void main(String[] args) {
PrintMessage pm1 = new PrintMessage();
PrintMessage pm2 = new PrintMessage();
new Thread(()->{
try {
pm1.printA();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{pm2.printB();},"B").start();
}
}
class PrintMessage{
public static synchronized void printA() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "A");
}
public static synchronized void printB(){
System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "B");
}
}
執行結果:
七、一個靜態同步方和一個普通方法,一個物件的執行順序
package demo_02;
// 八鎖問題
import java.util.concurrent.TimeUnit;
public class Test_01 {
public static void main(String[] args) {
PrintMessage pm = new PrintMessage();
new Thread(()->{
try {
pm.printA();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{pm.printB();},"B").start();
}
}
class PrintMessage{
public static synchronized void printA() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "A");
}
public void printB(){
System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "B");
}
}
執行結果:
相信大家如果認真看了前面的問題,就知道這個問題的原因了,因為普通方法並不會獲取鎖資源,所以執行緒B的普通方法可以直接執行,執行緒A睡眠3秒後再執行。
八、兩個物件,一個靜態同步方法,一個普通同步方法,執行順序
這個地方的關鍵是要明白鎖的物件鎖的是什麼?
package demo_02;
// 八鎖問題
import java.util.concurrent.TimeUnit;
public class Test_01 {
public static void main(String[] args) {
PrintMessage pm1 = new PrintMessage();
PrintMessage pm2 = new PrintMessage();
new Thread(()->{
try {
pm1.printA();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{pm2.printB();},"B").start();
}
}
class PrintMessage{
public static synchronized void printA() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "A");
}
public synchronized void printB(){
System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "B");
}
}
執行結果:
首先靜態同步方法鎖的是類模板(class檔案),而普通同步方法鎖的是PrintMessage的物件,所以兩個鎖的物件是不同的,兩個鎖並不衝突,自己鎖自己的,執行緒A睡眠3秒後輸出,執行緒B正常輸出,所以先輸出B執行緒再輸出A執行緒。
該部分內容是筆者參考狂神的學習視訊,做的一些總結以及自己的思考,如果有紕漏的地方,希望大家多多指出,一起學習!