簡介
本文將介紹8種同步方法的訪問場景,我們來看看這八種情況下,多執行緒訪問同步方法是否還是執行緒安全的,這些場景是多執行緒編程中經常遇到的,而且也是面試時高頻被問到的問題,所以不管是理論還是實踐,這些都是多執行緒場景必須要掌握的場景,
好了,話不多說,看正文吧,關注公眾號:Java架構師聯盟
場景一:兩個執行緒同時訪問同一個物件的同步方法
分析:這種情況是經典的物件鎖中的方法鎖,兩個執行緒爭奪同一個物件鎖,所以會相互等待,是執行緒安全的,
代碼實作
package com.syn;
/**
* @author :biws
* @date :Created in 2020/12/21 22:27
* @description:兩個執行緒同時訪問同一個物件的同步方法
*/
public class Condition1 implements Runnable {
private static Condition1 instance = new Condition1();
@Override
public void run() {
method();
}
//關鍵:synchronized可以保證此方法被順序執行,執行緒1執行完4秒鐘后,執行緒2再執行4秒,不加synchronized,執行緒1和執行緒2將同時執行
private synchronized void method() {
System.out.println("執行緒:" + Thread.currentThread().getName() + ",運行開始");
try {
//模擬執行一段操作,耗時4秒鐘
Thread.sleep(4000);
System.out.println("執行緒:" + Thread.currentThread().getName() + ",運行結束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// 模擬:同一個物件下,兩個執行緒,同步執行一個方法(串行執行則為執行緒安全,并行執行,則為執行緒不安全,)
Thread thread1 = new Thread(instance);
Thread thread2 = new Thread(instance);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()) {
}
System.out.println("測驗結束");
}
}
運行結果
結果分析
發現運行結果中,多個執行緒也是串行執行的,效果跟同步代碼塊鎖是一致的,
場景二:兩個執行緒同時訪問兩個物件的同步方法
這種場景就是物件鎖失效的場景,原因出在訪問的是兩個物件的同步方法,那么這兩個執行緒分別持有的兩個執行緒的鎖,所以是互相不會受限的,加鎖的目的是為了讓多個執行緒競爭同一把鎖,而這種情況多個執行緒之間不再競爭同一把鎖,而是分別持有一把鎖
代碼驗證:
package com.syn;
/**
* @author :biws
* @date :Created in 2020/12/21 22:10
* @description:兩個執行緒同時訪問兩個物件的同步方法
*/
public class Condition2 implements Runnable {
// 創建兩個不同的物件
static Condition2 instance1 = new Condition2();
static Condition2 instance2 = new Condition2();
@Override
public void run() {
method();
}
private synchronized void method() {
System.out.println("執行緒名:" + Thread.currentThread().getName() + ",運行開始");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行緒:" + Thread.currentThread().getName() + ",運行結束");
}
public static void main(String[] args) {
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance2);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()) {
}
System.out.println("測驗結束");
}
}
運行結果:
兩個執行緒是并行執行的,所以執行緒不安全,
代碼分析:
「問題在此:」兩個執行緒(thread1、thread2),訪問兩個物件(instance1、instance2)的同步方法(method()),兩個執行緒都有各自的鎖,不能形成兩個執行緒競爭一把鎖的局勢,所以這時,synchronized修飾的方法method()和不用synchronized修飾的效果一樣(不信去把synchronized關鍵字去掉,運行結果一樣),所以此時的method()只是個普通方法,
「如何解決這個問題:」若要使鎖生效,只需將method()方法用static修飾,這樣就形成了類鎖,多個實體(instance1、instance2)共同競爭一把類鎖,就可以使兩個執行緒串行執行了,
場景三:兩個執行緒同時訪問(一個或兩個)物件的靜態同步方法
這個場景解決的是場景二中出現的執行緒不安全問題,即用類鎖實作
代碼實作
package com.syn;
/**
* @author :biws
* @date :Created in 2020/12/21 22:32
* @description:靜態方法鎖的方式實作類鎖
*/
public class Condition3 implements Runnable {
// 這兩個實體,宣告為static,是因為要在main方法中測驗,與方法鎖無關
static Condition3 instance1 = new Condition3();
static Condition3 instance2 = new Condition3();
// 關鍵: static synchronized兩個關鍵字同時使用
private static synchronized void method() {
System.out.println("執行緒名:" + Thread.currentThread().getName() + ",運行開始");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行緒:" + Thread.currentThread().getName() + ",運行結束");
}
@Override
public void run() {
method();
}
public static void main(String[] args) {
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance2);
thread1.start();
thread2.start();
try {
//兩個執行緒必須都執行完畢后
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("兩個執行緒都已經執行完畢");
}
}
結果展示
場景四:兩個執行緒分別同時訪問(一個或兩個)物件的同步方法和非同步方法
這個場景是兩個執行緒其中一個訪問同步方法,另一個訪問非同步方法,此時程式會不會串行執行呢,也就是說是不是執行緒安全的呢?我們可以確定是執行緒不安全的,如果方法不加synchronized都是安全的,那就不需要同步方法了,
package com.syn;
/**
* @author :biws
* @date :Created in 2020/12/21 22:12
* @description:兩個執行緒同時訪問(一個或兩個)物件的靜態同步方法
*/
public class Condition4 implements Runnable {
static Condition4 instance = new Condition4();
@Override
public void run() {
//兩個執行緒訪問同步方法和非同步方法
if (Thread.currentThread().getName().equals("Thread-0")) {
//執行緒0,執行同步方法method0()
method0();
}
if (Thread.currentThread().getName().equals("Thread-1")) {
//執行緒1,執行非同步方法method1()
method1();
}
}
// 同步方法
private synchronized void method0() {
System.out.println("執行緒名:" + Thread.currentThread().getName() + ",同步方法,運行開始");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行緒:" + Thread.currentThread().getName() + ",同步方法,運行結束");
}
// 普通方法
private void method1() {
System.out.println("執行緒名:" + Thread.currentThread().getName() + ",普通方法,運行開始");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行緒:" + Thread.currentThread().getName() + ",普通方法,運行結束");
}
public static void main(String[] args) {
Thread thread1 = new Thread(instance);
Thread thread2 = new Thread(instance);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()) {
}
System.out.println("測驗結束");
}
}
運行結果:
兩個執行緒是并行執行的,所以是執行緒不安全的,
結果分析
「問題在于此:」 method1沒有被synchronized修飾,所以不會受到鎖的影響,即便是在同一個物件中,當然在多個實體中,更不會被鎖影響了,
場景五:兩個執行緒訪問同一個物件中的同步方法,同步方法又呼叫一個非同步方法
我們來實驗下這個場景,用兩個執行緒呼叫同步方法,在同步方法中呼叫普通方法;再用一個執行緒直接呼叫普通方法,看看是否是執行緒安全的?
package com.syn;
/**
* @author :biws
* @date :Created in 2020/12/21 22:14
* @description:兩個執行緒訪問同一個物件中的同步方法,同步方法又呼叫一個非同步方法
*/
public class Condition5 implements Runnable {
static Condition5 instance = new Condition5();
@Override
public void run() {
if (Thread.currentThread().getName().equals("Thread-0")) {
//直接呼叫普通方法
method2();
} else {
// 先呼叫同步方法,在同步方法內呼叫普通方法
method1();
}
}
// 同步方法
private static synchronized void method1() {
System.out.println("執行緒名:" + Thread.currentThread().getName() + ",同步方法,運行開始");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行緒:" + Thread.currentThread().getName() + ",同步方法,運行結束,開始呼叫普通方法");
method2();
}
// 普通方法
private static void method2() {
System.out.println("執行緒名:" + Thread.currentThread().getName() + ",普通方法,運行開始");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行緒:" + Thread.currentThread().getName() + ",普通方法,運行結束");
}
public static void main(String[] args) {
// 此執行緒直接呼叫普通方法
Thread thread0 = new Thread(instance);
// 這兩個執行緒直接呼叫同步方法
Thread thread1 = new Thread(instance);
Thread thread2 = new Thread(instance);
thread0.start();
thread1.start();
thread2.start();
while (thread0.isAlive() || thread1.isAlive() || thread2.isAlive()) {
}
System.out.println("測驗結束");
}
}
運行結果:
結果分析:
我們可以看出,普通方法被兩個執行緒并行執行,不是執行緒安全的,這是為什么呢?
因為如果非同步方法,有任何其他執行緒直接呼叫,而不是僅在呼叫同步方法時,才呼叫非同步方法,此時會出現多個執行緒并行執行非同步方法的情況,執行緒就不安全了,
對于同步方法中呼叫非同步方法時,要想保證執行緒安全,就必須保證非同步方法的入口,僅出現在同步方法中,但這種控制方式不夠優雅,若被不明情況的人直接呼叫非同步方法,就會導致原有的執行緒同步不再安全,所以不推薦大家在專案中這樣使用,但我們要理解這種情況,并且我們要用語意明確的、讓人一看就知道這是同步方法的方式,來處理執行緒安全的問題,
所以,最簡單的方式,是在非同步方法上,也加上synchronized關鍵字,使其變成一個同步方法,這樣就變成了《場景五:兩個執行緒同時訪問同一個物件的不同的同步方法》,這種場景下,大家就很清楚的看到,同一個物件中的兩個同步方法,不管哪個執行緒呼叫,都是執行緒安全的了,
場景六:兩個執行緒同時訪問同一個物件的不同的同步方法
這個場景也是在探討物件鎖的作用范圍,物件鎖的作用范圍是物件中的所有同步方法,所以,當訪問同一個物件中的多個同步方法時,結論是:
package com.syn;
/**
* @author :biws
* @date :Created in 2020/12/21 22:16
* @description:兩個執行緒同時訪問同一個物件的不同的同步方法
*/
public class Condition6 implements Runnable {
static Condition6 instance = new Condition6();
@Override
public void run() {
if (Thread.currentThread().getName().equals("Thread-0")) {
//執行緒0,執行同步方法method0()
method0();
}
if (Thread.currentThread().getName().equals("Thread-1")) {
//執行緒1,執行同步方法method1()
method1();
}
}
private synchronized void method0() {
System.out.println("執行緒名:" + Thread.currentThread().getName() + ",同步方法0,運行開始");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行緒:" + Thread.currentThread().getName() + ",同步方法0,運行結束");
}
private synchronized void method1() {
System.out.println("執行緒名:" + Thread.currentThread().getName() + ",同步方法1,運行開始");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行緒:" + Thread.currentThread().getName() + ",同步方法1,運行結束");
}
//運行結果:串行
public static void main(String[] args) {
Thread thread1 = new Thread(instance);
Thread thread2 = new Thread(instance);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()) {
}
System.out.println("測驗結束");
}
}
運行結果:
結果分析:
兩個方法(method0()和method1())的synchronized修飾符,雖沒有指定鎖物件,但默認鎖物件為this物件為鎖物件, 所以對于同一個實體(instance),兩個執行緒拿到的鎖是同一把鎖,此時同步方法會串行執行,這也是synchronized關鍵字的可重入性的一種體現,
場景七:兩個執行緒分別同時訪問靜態synchronized和非靜態synchronized方法
這種場景的本質也是在探討兩個執行緒獲取的是不是同一把鎖的問題,靜態synchronized方法屬于類鎖,鎖物件是(*.class)物件,非靜態synchronized方法屬于物件鎖中的方法鎖,鎖物件是this物件,兩個執行緒拿到的是不同的鎖,自然不會相互影響,
代碼實作:
package com.syn;
/**
* @author :biws
* @date :Created in 2020/12/21 22:20
* @description:兩個執行緒分別同時訪問靜態synchronized和非靜態synchronized方法
*/
public class Condition7 implements Runnable {
static Condition7 instance = new Condition7();
@Override
public void run() {
if (Thread.currentThread().getName().equals("Thread-0")) {
//執行緒0,執行靜態同步方法method0()
method0();
}
if (Thread.currentThread().getName().equals("Thread-1")) {
//執行緒1,執行非靜態同步方法method1()
method1();
}
}
// 重點:用static synchronized 修飾的方法,屬于類鎖,鎖物件為(*.class)物件,
private static synchronized void method0() {
System.out.println("執行緒名:" + Thread.currentThread().getName() + ",靜態同步方法0,運行開始");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行緒:" + Thread.currentThread().getName() + ",靜態同步方法0,運行結束");
}
// 重點:synchronized 修飾的方法,屬于方法鎖,鎖物件為(this)物件,
private synchronized void method1() {
System.out.println("執行緒名:" + Thread.currentThread().getName() + ",非靜態同步方法1,運行開始");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行緒:" + Thread.currentThread().getName() + ",非靜態同步方法1,運行結束");
}
//運行結果:并行
public static void main(String[] args) {
//問題原因: 執行緒1的鎖是類鎖(*.class)物件,執行緒2的鎖是方法鎖(this)物件,兩個執行緒的鎖不一樣,自然不會互相影響,所以會并行執行,
Thread thread1 = new Thread(instance);
Thread thread2 = new Thread(instance);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()) {
}
System.out.println("測驗結束");
}
}
運行結果:
場景八:同步方法拋出例外后,JVM會自動釋放鎖的情況
本場景探討的是synchronized釋放鎖的場景:
所以,在一個執行緒的同步方法中出現例外的時候,會釋放鎖,另一個執行緒得到鎖,繼續執行,而不會出現一個執行緒拋出例外后,另一個執行緒一直等待獲取鎖的情況,這是因為JVM在同步方法拋出例外的時候,會自動釋放鎖物件,
代碼實作:
package com.syn;
/**
* @author :biws
* @date :Created in 2020/12/21 22:22
* @description:同步方法拋出例外后,JVM會自動釋放鎖的情況
*/
public class Condition8 implements Runnable {
private static Condition8 instance = new Condition8();
@Override
public void run() {
if (Thread.currentThread().getName().equals("Thread-0")) {
//執行緒0,執行拋例外方法method0()
method0();
}
if (Thread.currentThread().getName().equals("Thread-1")) {
//執行緒1,執行正常方法method1()
method1();
}
}
private synchronized void method0() {
System.out.println("執行緒名:" + Thread.currentThread().getName() + ",運行開始");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//同步方法中,當拋出例外時,JVM會自動釋放鎖,不需要手動釋放,其他執行緒即可獲取到該鎖
System.out.println("執行緒名:" + Thread.currentThread().getName() + ",拋出例外,釋放鎖");
throw new RuntimeException();
}
private synchronized void method1() {
System.out.println("執行緒名:" + Thread.currentThread().getName() + ",運行開始");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行緒:" + Thread.currentThread().getName() + ",運行結束");
}
public static void main(String[] args) {
Thread thread1 = new Thread(instance);
Thread thread2 = new Thread(instance);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()) {
}
System.out.println("測驗結束");
}
}
運行結果:
結果分析:
可以看出執行緒還是串行執行的,說明是執行緒安全的,而且出現例外后,不會造成死鎖現象,JVM會自動釋放出現例外執行緒的鎖物件,其他執行緒獲取鎖繼續執行,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/238867.html
標籤:Java
上一篇:回歸本源:從最基礎的SSM框架開始,梳理自己的Java基礎
下一篇:Java基本概念:例外
