Java多線程(二)

来源:https://www.cnblogs.com/xiaozhao01/archive/2022/08/05/16555150.html
-Advertisement-
Play Games

Java多線程(二) 四、線程的同步 4.1 線程同步的引入: 多線程出現了安全問題。 問題的原因: 當多條語句在操作同一個線程共用數據時,一個線程對多條語句只執行了一部分,還沒有執行完,另一個線程參與進來執行。導致共用數據的錯誤。例如:買票問題、銀行卡消費問題等等。 解決辦法: 對多條操作共用數據 ...


Java多線程(二)

目錄

四、線程的同步

4.1 線程同步的引入:

  • 多線程出現了安全問題
  • 問題的原因: 當多條語句在操作同一個線程共用數據時,一個線程對多條語句只執行了一部分,還沒有執行完,另一個線程參與進來執行。導致共用數據的錯誤。例如:買票問題、銀行卡消費問題等等。
  • 解決辦法: 對多條操作共用數據的語句,只能讓一個線程都執行完,在執行過程中,其他線程不可以參與執行

​ 所以,Java 對於多線程的安全問題提供了專業的解決方式:同步機制

4.2 線程同步的方式之一:同步代碼塊

  1. 語法格式:
synchronized (對象/同步監視器){	//得到對象的鎖,才能操作同步代碼
	// 需要被同步的代碼
}

說明:

​ (1)操作共用數據的代碼,即為需要被同步的代碼。 --> 不能包含代碼多了,也不能包含代碼少了。

​ (2)共用數據:多個線程共同操作的變數。

​ (3)同步監視器,俗稱:鎖。任何一個類的對象,都可以充當鎖

​ (4)要求:多個線程必須要共用同一把鎖

  1. 使用同步代碼塊解決在實現 Runnable 介面的方式創建多線程的線程安全問題
//	例子:創建三個視窗賣票,總票數為100張.使用實現Runnable介面的方式
class Window1 implements Runnable{

    private int ticket = 100;
//    Object obj = new Object();
    @Override
    public void run() {
//      Object obj = new Object();
        while(true){
            // 同步代碼塊---begin
            synchronized (this){	// 此時的this:唯一的 Window1 的對象w   //方式二:synchronized (object) {

                if (ticket > 0) {

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
            // 同步代碼塊---end
        }
    }
}

public class WindowTest1 {
    public static void main(String[] args) {
        Window1 w = new Window1();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("視窗1");
        t2.setName("視窗2");
        t3.setName("視窗3");

        t1.start();
        t2.start();
        t3.start();
    }
}
  1. 使用同步代碼塊解決繼承 Thread 類的方式創建多線程的線程安全問題
class Window2 extends Thread{

    private static int ticket = 100;
//    private static Object obj = new Object();

    @Override
    public void run() {

        while(true){

            synchronized (Window2.class){//	Window2.class表示window2這一個類,只會載入一次
//       方式二:synchronized (obj){	               
//              synchronized (this){	錯誤的方式:this分別代表著t1,t2,t3三個對象

                if(ticket > 0){

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(getName() + ":賣票,票號為:" + ticket);
                    ticket--;
                }else{
                    break;
                }
            }
        }
    }
}

public class WindowTest2 {
    public static void main(String[] args) {
        Window2 t1 = new Window2();
        Window2 t2 = new Window2();
        Window2 t3 = new Window2();

        t1.setName("視窗1");
        t2.setName("視窗2");
        t3.setName("視窗3");

        t1.start();
        t2.start();
        t3.start();
    }
}

4.3 線程同步的方式之二:同步方法

  1. 同步方法:即將操作共用數據的代碼完整的聲明在一個方法中,將該方法聲明為同步方法。

  2. 語法格式:

//	將synchronized放在方法聲明中,一般放在許可權符和返回類型之間,表示整個方法為同步方法
public synchronized void 方法名 (String name){ 
	// 需要被同步的代碼
}

說明:

(1)同步方法仍然涉及到同步監視器,只是不需要我們顯式的聲明。

(2)非靜態的同步方法,同步監視器是:this。

(3)靜態的同步方法,同步監視器是:當前類本身。

  1. 使用同步方法解決在實現 Runnable 介面的方式創建多線程的線程安全問題
class Window3 implements Runnable {

    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            show();
        }
    }

    private synchronized void show(){	//同步監視器:this

        if (ticket > 0) {
            
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + ticket);
            ticket--;
            }
    }
}

public class WindowTest3 {
    public static void main(String[] args) {
        Window3 w = new Window3();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("視窗1");
        t2.setName("視窗2");
        t3.setName("視窗3");

        t1.start();
        t2.start();
        t3.start();
    }
}
  1. 使用同步方法解決繼承 Thread 類的方式創建多線程的線程安全問題
class Window4 extends Thread {

    private static int ticket = 100;

    @Override
    public void run() {

        while (true) {
            show();
        }
    }
    
    private static synchronized void show(){	//同步監視器:Window4.class
        //private synchronized void show(){ //同步監視器:t1,t2,t3。此種解決方式是錯誤的
        if (ticket > 0) {

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + ticket);
            ticket--;
        }
    }
}


public class WindowTest4 {
    public static void main(String[] args) {
        Window4 t1 = new Window4();
        Window4 t2 = new Window4();
        Window4 t3 = new Window4();

        t1.setName("視窗1");
        t2.setName("視窗2");
        t3.setName("視窗3");

        t1.start();
        t2.start();
        t3.start();

    }
}

總結:

通過上面四個售票例子可以看出:

在實現 Runnable 介面創建多線程的方式中,我們可以考慮使用 this 充當同步監視器。

而在繼承Thread類創建多線程的方式一般不使用 this 充當同步監視器,因為每個線程的 this 為該線程的實例對象,不滿足多個線程共用一把鎖,所以一般考慮用當前類本身充當。

4.4 同步的優勢與局限:

  • 優勢:解決了線程的安全問題。

  • 局限: 操作同步代碼時,只能有一個線程參與,其他線程等待。相當於是一個單線程的過程,效率低。也可能會造成死鎖問題。

4.5 線程安全的單例模式之懶漢式

public class BankTest {

}

class Bank{

    private Bank(){}

    private static Bank instance = null;

    public static Bank getInstance(){
        //方式一:效率稍差
//        synchronized (Bank.class) {
//            if(instance == null){
//                instance = new Bank();
//            }
//            return instance;
//        }
        //方式二:效率更高
        if(instance == null){
            synchronized (Bank.class) {
                if(instance == null){
                    instance = new Bank();
                }
            }
        }
        return instance;
    }
}

/*
	方式二效率更高的原因:
	不需要每個線程都要進去同步方法裡面去,可能只有最前面幾個進程進入同步方法。
	而方式一是所有線程都要進入同步方法導致效率較低。
*/

4.6 同步鎖機制:

  • 同步機制中的鎖在《Thinking in Java》中,是這麼說的:對於併發工作,你需要某種方式來防止兩個任務訪問相同的資源(其實就是共用資源競爭)。防止這種衝突的方法就是當資源被一個任務使用時,在其上加鎖。第一個訪問某項資源的任務必須鎖定這項資源,使其他任務在其被解鎖之前,就無法訪問它了,而在其被解鎖之時,另一個任務就可以鎖定並使用它了。

  • synchronized的鎖是什麼?

    1. 任意對象都可以作為同步鎖。所有對象都自動含有單一的鎖(監視器)。

    2. 同步方法的鎖:靜態方法(類名.class)、非靜態方法(this)。

    3. 同步代碼塊:可以自己指定,很多時候也是指定為this或類名.class。

  • 註意:

    1. 必須確保使用同一個資源的多個線程共用一把鎖,否則就無法保證共用資源的安全 。
    2. 一個線程類中的所有靜態方法共用同一把鎖(類名.class),所有非靜態方法共用同一把鎖(this),同步代碼塊(指定需謹慎)。

4.7 釋放鎖的操作:

  1. 當前線程的同步方法、同步代碼塊執行結束。
  2. 當前線程在同步代碼塊、同步方法中遇到 break、return 終止了該代碼塊、該方法的繼續執行。
  3. 當前線程在同步代碼塊、同步方法中出現了未處理的 Error 或 Exception,導致異常結束。
  4. 當前線程在同步代碼塊、同步方法中執行了線程對象的 wait() 方法,當前線程暫停,並釋放鎖。

4.8 不會釋放鎖的操作:

  1. 線程執行同步代碼塊或同步方法時,程式調用 Thread.sleep()、 Thread.yield() 方法暫停當前線程的執行。

  2. 線程執行同步代碼塊時,其他線程調用了該線程的 suspend() 方法將該線程掛起,該線程不會釋放鎖(同步監視器)。

    應儘量避免使用suspend()和resume()來控制線程

4.9 線程的死鎖問題

  1. 死鎖:

    • 不同的線程分別占用對方需要的同步資源不放棄,都在等待對方放棄自己需要的同步資源,就形成了線程的死鎖。
    • 出現死鎖後,不會出現異常,不會出現提示,只是所有的線程都處於阻塞狀態,無法繼續。
  2. 解決方法:

    • 專門的演算法、原則。
    • 儘量減少同步資源的定義。
    • 儘量避免嵌套同步。
  3. 例子:

//死鎖的演示
class A {
	public synchronized void foo(B b) { //同步監視器:A類的對象:a
		System.out.println("當前線程名: " + Thread.currentThread().getName()
				+ " 進入了A實例的foo方法"); // ①
//		try {
//			Thread.sleep(200);
//		} catch (InterruptedException ex) {
//			ex.printStackTrace();
//		}
		System.out.println("當前線程名: " + Thread.currentThread().getName()
				+ " 企圖調用B實例的last方法"); // ③
		b.last();
	}

	public synchronized void last() {//同步監視器:A類的對象:a
		System.out.println("進入了A類的last方法內部");
	}
}

class B {
	public synchronized void bar(A a) {//同步監視器:b
		System.out.println("當前線程名: " + Thread.currentThread().getName()
				+ " 進入了B實例的bar方法"); // ②
//		try {
//			Thread.sleep(200);
//		} catch (InterruptedException ex) {
//			ex.printStackTrace();
//		}
		System.out.println("當前線程名: " + Thread.currentThread().getName()
				+ " 企圖調用A實例的last方法"); // ④
		a.last();
	}

	public synchronized void last() {//同步監視器:b
		System.out.println("進入了B類的last方法內部");
	}
}

public class DeadLock implements Runnable {
	A a = new A();
	B b = new B();

	public void init() {
		Thread.currentThread().setName("主線程");
		// 調用a對象的foo方法
		a.foo(b);
		System.out.println("進入了主線程之後");
	}

	public void run() {
		Thread.currentThread().setName("副線程");
		// 調用b對象的bar方法
		b.bar(a);
		System.out.println("進入了副線程之後");
	}

	public static void main(String[] args) {
		DeadLock dl = new DeadLock();
		new Thread(dl).start();

		dl.init();
	}
}
  1. 註意:

    不是程式運行成功就說明程式裡面沒有死鎖問題,很多時候出現死鎖是一個概率問題。在開發中應儘量避免死鎖情況。

4.10 線程同步的方式之三:Lock鎖

  1. JDK 5.0 開始,Java提供了更強大的線程同步機制——通過顯式定義同步鎖對象來實現同步。同步鎖使用 Lock 對象充當。

  2. java.util.concurrent.locks.Lock 介面是控制多個線程對共用資源進行訪問的工具。鎖提供了對共用資源的獨占訪問,每次只能有一個線程對 Lock 對象加鎖,線程開始訪問共用資源之前應先獲得 Lock 對象。

  3. ReentrantLock 類實現了 Lock ,它擁有與 synchronized 相同的併發性和 記憶體語義,在實現線程安全的控制中,比較常用的是ReentrantLock,可以 顯式加鎖、釋放鎖。

  4. 語法:

class A{
    //1.實例化ReentrantLock
	private final ReentrantLock lock = new ReenTrantLock();
    
	public void m(){
        //2.調用鎖定方法lock()
		lock.lock();
		try{
			//保證線程安全的代碼;
		}
		finally{
            //3.調用解鎖方法:unlock()
			lock.unlock(); 
		}
	}
}

  1. (面試題)synchronized 與 Lock 的對比

    • 相同:二者都可以解決線程安全問題。
    • 不同:
      1. Lock 是顯式鎖(手動開啟和關閉鎖),synchronized 是隱式鎖,出了作用域自動釋放。
      2. Lock 只有代碼塊鎖,synchronized 有代碼塊鎖和方法鎖。
      3. 使用 Lock 鎖,JVM 將花費較少的時間來調度線程,性能更好。並且具有更好的擴展性(提供更多的子類)。
  2. 優先使用順序:

Lock —— 同步代碼塊(已經進入了方法體,分配了相應資源) —— 同步方法(在方法體之外)

4.11 (簡單介紹)公平鎖和非公平鎖

  1. 公平鎖(Fair):加鎖前檢查是否有排隊等待的線程,優先排隊等待的線程,先來先得。

    非公平鎖(Nonfair):加鎖時不考慮排隊等待問題,直接嘗試獲取鎖,獲取不到自動到隊尾等待。

  2. Java中的 ReentrantLock 預設的 lock() 方法採用的是非公平鎖。

//	源碼:
//	ReentrantLock當中的lock()方法,是通過static內部類sync來進行鎖操作
public void lock() 
{
     sync.lock();
}
-------------------------------------------------------------------
//定義成final型的成員變數,在構造方法中進行初始化 
private final Sync sync;
//無參數預設非公平鎖
public ReentrantLock() 
{
    sync = new NonfairSync();
}
//根據參數初始化為公平鎖或者非公平鎖 
public ReentrantLock(boolean fair) 
{
    sync = fair ? new FairSync() : new NonfairSync();
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1. 事件起因 之前做一個駕照考題的項目,有一個這樣的問題,每當我選好了科目和駕照類型後(如圖1),點擊開始考試就會跳到考試頁面(Test.tsx),並且在Test組件中對我架設的中間層發起請求獲取數據(如圖2)。 如果用戶手滑的話不小心點到了左上角的返回,或者狠狠地把屏幕往右滑動一下的話,都會返回 ...
  • 1. 事件起因 事情是這樣的,我之前在做一個仿網易雲的項目,想實現一個功能,就是點擊歌曲列表組件(MusicList)中每一個item時,底部播放欄(FooterMusic)就能夠獲取我所點擊的這首歌曲的所有信息(如圖1到圖2),但是底部播放欄是直接放在外殼組件App.vue中固定定位的,歌曲列表組 ...
  • 引言 當今社會對軟體需求在相當長的時間里將保持旺盛,而軟體開發周期長、個性化難、順應需求變更不變,如何可以才能將軟體開發定製變得簡單方便快捷呢? 1. 軟體開發設計現狀 目前軟體的開發設計都是定向開發,即根據項目需求將相關的數據關係、業務邏輯、功能模塊及介面插件等揉合在一起並與人機交互整體開發(若涉 ...
  • 單元測試JUnit 單元測試的目的是針對方法進行測試, **JUnit的兩個要點:**①必須是公開的,無參數,無返回值的方法 ②測試方法必須使用@Test註解標記 public class JUnitTest { @Test public void Testusername() { way way ...
  • 目錄 一.簡介 二.效果演示 三.源碼下載 四.猜你喜歡 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 轉場 零基礎 O ...
  • 多線程併發 在多核CPU中,利用多線程併發編程,可以更加充分地利用每個核的資源 在Java中,一個應用程式對應著一個JVM實例(也有地方稱為JVM進程),如果程式沒有主動創建線程,則只會創建一個主線程。但這不代表JVM中只有一個線程,JVM實例在創建的時候,同時會創建很多其他的線程(比如垃圾收集器線 ...
  • Java多線程(三) 五、線程的通信 5.1 wait() 與 notify() 和 notifyAll() 介紹: wait():令當前線程掛起並放棄CPU、同步資源並等待,使別的線程可訪問並修改共用資源,而當前線程排隊等候其他線程調用notify() 或 notifyAll() 方法喚醒,喚醒後 ...
  • (目錄) turtle庫的介紹 turtle庫也叫海龜庫,1969年誕生,是turtle繪圖體系的Python實現。turtle庫是Python語言的標準庫之一,是入門級的圖形繪製函數庫。 turtle庫的使用 方法一:import turtle 這種方法在之後每次引用turtle庫裡面的函數都需要 ...
一周排行
    -Advertisement-
    Play Games
  • JWT(JSON Web Token)是一種用於在網路應用之間傳遞信息的開放標準(RFC 7519)。它使用 JSON 對象在安全可靠的方式下傳遞信息,通常用於身份驗證和信息交換。 在Web API中,JWT通常用於對用戶進行身份驗證和授權。當用戶登錄成功後,伺服器會生成一個Token並返回給客戶端 ...
  • 老周在幾個世紀前曾寫過樹莓派相關的 iOT 水文,之所以沒寫 Nano Framework 相關的內容,是因為那時候這貨還不成熟,可玩性不高。不過,這貨現在已經相對完善,老周都把它用在項目上了——第一個是自製的智能插座,這個某寶上50多塊可以買到,搜“esp32 插座”就能找到。一種是 86 型盒子 ...
  • 引言 上一篇我們創建了一個Sample.Api項目和Sample.Repository,並且帶大家熟悉了一下Moq的概念,這一章我們來實戰一下在xUnit項目使用依賴註入。 Xunit.DependencyInjection Xunit.DependencyInjection 是一個用於 xUnit ...
  • 在 Avalonia 中,樣式是定義控制項外觀的一種方式,而控制項主題則是一組樣式和資源,用於定義應用程式的整體外觀和感覺。本文將深入探討這些概念,並提供示例代碼以幫助您更好地理解它們。 樣式是什麼? 樣式是一組屬性,用於定義控制項的外觀。它們可以包括背景色、邊框、字體樣式等。在 Avalonia 中,樣 ...
  • 在處理大型Excel工作簿時,有時候我們需要在工作表中凍結窗格,這樣可以在滾動查看數據的同時保持某些行或列固定不動。凍結窗格可以幫助我們更容易地導航和理解複雜的數據集。相反,當你不需要凍結窗格時,你可能需要解凍它們以獲得完整的視野。 下麵將介紹如何使用免費.NET庫通過C#實現凍結Excel視窗以鎖 ...
  • .NET 部署 IIS 的簡單步驟一: 下載 dotnet-hosting-x.y.z-win.exe ,下載地址:.NET Downloads (Linux, macOS, and Windows) (microsoft.com) .NET 部署 IIS 的簡單步驟二: 選擇對應的版本,點擊進入詳 ...
  • 拓展閱讀 資料庫設計工具-08-概覽 資料庫設計工具-08-powerdesigner 資料庫設計工具-09-mysql workbench 資料庫設計工具-10-dbdesign 資料庫設計工具-11-dbeaver 資料庫設計工具-12-pgmodeler 資料庫設計工具-13-erdplus ...
  • 初識STL STL,(Standard Template Library),即"標準模板庫",由惠普實驗室開發,STL中提供了非常多對信息學奧賽很有用的東西。 vector vetor是STL中的一個容器,可以看作一個不定長的數組,其基本形式為: vector<數據類型> 名字; 如: vector ...
  • 前言 最近自己做了個 Falsk 小項目,在部署上伺服器的時候,發現雖然不乏相關教程,但大多都是將自己項目代碼複製出來,不講核心邏輯,不太簡潔,於是將自己部署的經驗寫成內容分享出來。 uWSGI 簡介 uWSGI: 一種實現了多種協議(包括 uwsgi、http)並能提供伺服器搭建功能的 Pytho ...
  • 1 文本Embedding 將整個文本轉化為實數向量的技術。 Embedding優點是可將離散的詞語或句子轉化為連續的向量,就可用數學方法來處理詞語或句子,捕捉到文本的語義信息,文本和文本的關係信息。 ◉ 優質的Embedding通常會讓語義相似的文本在空間中彼此接近 ◉ 優質的Embedding相 ...