java基礎(五):談談java中的多執行緒

鄧叔叔很年輕發表於2019-02-26

1.多執行緒

1.1.多執行緒介紹

  學習多執行緒之前,我們先要了解幾個關於多執行緒有關的概念。

  程式:正在執行的程式。確切的來說,當一個程式進入記憶體執行,即變成一個程式,程式是處於執行過程中的程式,並且具有一定獨立功能,程式是系統進行資源分配和排程的一個獨立單位。程式是正在執行的程式,程式負責給程式分配記憶體空間,而每一個程式都是由程式程式碼組成的,這些程式碼在程式中執行的流程就是執行緒。

  執行緒:執行緒是程式中的一個執行單元,負責當前程式中程式的執行,一個程式中至少有一個執行緒。一個程式中是可以有多個執行緒的,這個應用程式也可以稱之為多執行緒程式。

  簡而言之:一個程式執行後至少有一個程式,一個程式中可以包含多個執行緒,但至少有一個執行緒。什麼是多執行緒呢?即就是一個程式中有多個執行緒在同時執行。

1.2.多執行緒執行原理

  大部分作業系統都支援多程式併發執行,現在的作業系統幾乎都支援同時執行多個任務。比如:現在我們上課一邊使用編輯器,一邊使用錄屏軟體,同時還開著畫圖板,dos視窗等軟體。感覺這些軟體好像在同時執行著。

  其實這些軟體在某一時刻,只會執行一個程式。這是為什麼呢?這是由於CPU(中央處理器)在做著高速的切換而導致的。對於CPU而言,它在某個時間點上,只能執行一個程式,即就是說只能執行一個程式,CPU不斷地在這些程式之間切換。只是我們自己感覺不到。為什麼我們會感覺不到呢?這是因為CPU的執行速度相對我們的感覺實在太快了,雖然CPU在多個程式之間輪換執行,但我們自己感到好像多個程式在同時執行。

  多執行緒真的能提高效率嗎?其實並不是這樣的,因為我們知道,CPU會在多個程式之間做著切換,如果我們開啟的程式過多,CPU切換到每一個程式的時間也會變長,我們也會感覺機器執行變慢。所以合理的使用多執行緒可以提高效率,但是大量使用,並不能給我們帶來效率上的提高。

1.3.主執行緒

  回想我們以前學習中寫過的程式碼,當我們在dos命令列中輸入java空格類名回車後,啟動JVM,並且載入對應的class檔案。虛擬機器並會從main方法開始執行我們的程式程式碼,一直把main方法的程式碼執行結束。如果在執行過程遇到迴圈時間比較長的程式碼,那麼在迴圈之後的其他程式碼是不會被執行的。如下程式碼演示:

class Demo
{
    String name;
    Demo(String name)
    {
        this.name = name;
    }
    void show()
    {
        for (int i=1;i<=20 ;i++ )
        {
            System.out.println("name="+name+",i="+i);
        }
    }
}
class ThreadDemo 
{
    public static void main(String[] args) 
    {
        Demo d = new Demo("小強");
        Demo d2 = new Demo("旺財");
        d.show();        
        d2.show();
        System.out.println("Hello World!");
    }
}
複製程式碼

  若在上述程式碼中show方法中的迴圈執行次數很多,這時書寫在d.show();下面的程式碼是不會執行的,並且在dos視窗會看到不停的輸出name=小強,i=值,這樣的語句。為什麼會這樣呢?

  原因是:jvm啟動後,必然有一個執行路徑(執行緒)從main方法開始的。一直執行到main方法結束。這個執行緒在java中稱之為主執行緒。當主執行緒在這個程式中執行時,如果遇到了迴圈而導致程式在指定位置停留時間過長,無法執行下面的程式。

  可不可以實現一個主執行緒負責執行其中一個迴圈,由另一個執行緒負責其他程式碼的執行。實現多部分程式碼同時執行。這就是多執行緒技術可以解決的問題。

1.4.如何建立執行緒

1.4.1.建立執行緒方式一:繼承Thread類

  該如何建立執行緒呢?通過API中的英文Thread的搜尋,查到Thread類。通過閱讀Thread類中的描述。建立新執行執行緒有兩種方法。一種方法是將類宣告為 Thread 的子類。該子類應重寫 Thread 類的 run 方法。接下來可以分配並啟動該子類的例項。

  建立執行緒的步驟:

  1. 定義一個類繼承Thread。

  2. 重寫run方法。

  3. 建立子類物件,就是建立執行緒物件。

  4. 呼叫start方法,開啟執行緒並讓執行緒執行,同時還會告訴jvm去呼叫run方法。

class Demo extends Thread  //繼承Thread
{
    String name;
    Demo(String name)
    {
        this.name = name;
    }
    //複寫其中的run方法
    public void run()
    {
        for (int i=1;i<=20 ;i++ )
        {
            System.out.println("name="+name+",i="+i);
        }
    }
}
class ThreadDemo 
{
    public static void main(String[] args) 
    {
        //建立兩個執行緒任務
        Demo d = new Demo("小強");
        Demo d2 = new Demo("旺財");
        //d.run(); 這裡仍然是主執行緒在呼叫run方法,並沒有開啟兩個執行緒
        //d2.run();
        d2.start();//開啟一個執行緒
        d.run();//主執行緒在呼叫run方法
    }
}
複製程式碼

  列印部分結果:由於多執行緒操作,輸出資料會有所不同

    name=旺財,i=1

    name=小強,i=1

    name=旺財,i=2

    name=小強,i=2

    name=小強,i=3

    name=旺財,i=3

    name=旺財,i=4

    name=旺財,i=5

    name=旺財,i=6

    name=旺財,i=7

    ..........

  思考:執行緒物件呼叫 run方法和呼叫start方法區別?

執行緒物件呼叫run方法不開啟執行緒。僅是物件呼叫方法。執行緒物件呼叫start開啟執行緒,並讓jvm呼叫run方法在開啟的執行緒中執行。

1.4.2.繼承Thread類原理

  為什麼要繼承Thread類,並呼叫其的start方法才能開啟執行緒呢?

  繼承Thread類:因為Thread類描述執行緒事物,具備執行緒應該有功能。

  那為什麼不直接建立Thread類的物件呢?

1 Thread t1 = new Thread();
2 t1.start();//這樣做沒有錯,但是該start呼叫的是Thread類中的run方法,而這個run方法沒有做什麼事情,更重要的是這個run方法中並沒有定義我們需要讓執行緒執行的程式碼。
複製程式碼

  建立執行緒的目的是什麼?

  是為了建立單獨的執行路徑,讓多部分程式碼實現同時執行。也就是說執行緒建立並執行需要給定的程式碼(執行緒的任務)。對於之前所講的主執行緒,它的任務定義在main函式中。自定義執行緒需要執行的任務都定義在run方法中。Thread類中的run方法內部的任務並不是我們所需要,只有重寫這個run方法,既然Thread類已經定義了執行緒任務的位置,只要在位置中定義任務程式碼即可。所以進行了重寫run方法動作。

1.5.多執行緒的記憶體圖解

  多執行緒執行時,到底在記憶體中是如何執行的呢?

  以上個程式為例,進行圖解說明:

  多執行緒執行時,在棧記憶體中,其實每一個執行執行緒都有一片自己所屬的棧記憶體空間。進行方法的壓棧彈棧

java基礎(五):談談java中的多執行緒

  當執行執行緒的任務結束了,執行緒自動在棧記憶體中釋放了。但是當所有的執行執行緒都結束了,那麼程式就結束了。

1.6.獲取執行緒名稱

  開啟的執行緒都會有自己的獨立執行棧記憶體,那麼這些執行的執行緒的名字是什麼呢?該如何獲取呢?既然是執行緒的名字,按照物件導向的特點,是哪個物件的屬性和誰的功能,那麼我們就去找那個物件就可以了。查閱Thread類的API文件發現有個方法是獲取當前正在執行的執行緒物件。還有個方法是獲取當前執行緒物件的名稱。既然找到了,我們就可以試試。

   Thread.currentThread()獲取當前執行緒物件

   Thread.currentThread().getName();獲取當前執行緒物件的名稱

class Demo extends Thread  //繼承Thread
{
    String name;
    Demo(String name)
    {
        this.name = name;
    }
    //複寫其中的run方法
    public void run()
    {
        for (int i=1;i<=20 ;i++ )
        {
            System.out.println("name="+name+","+Thread.currentThread().getName()+",i="+i);
        }
    }
}
class ThreadDemo 
{
    public static void main(String[] args) 
    {
        //建立兩個執行緒任務
        Demo d = new Demo("小強");
        Demo d2 = new Demo("旺財");
        d2.start();//開啟一個執行緒
        d.run();//主執行緒在呼叫run方法
    }
}
複製程式碼

原來主執行緒的名稱: main

自定義的執行緒: Thread-0 執行緒多個時,數字順延。Thread-1......

進行多執行緒程式設計時不要忘記了Java程式執行時從主執行緒開始,main方法的方法體就是主執行緒的執行緒執行體。

1.7.建立執行緒的第二種方式

  掌握瞭如何建立執行緒物件,以及開啟執行緒後,記得在查閱API時,還說了有第二種開啟執行緒的方式,那麼第二種是什麼呢?

1.7.1.實現Runnable介面

  繼續檢視API發現,建立執行緒的另一種方法是宣告實現 Runnable 介面的類。該類然後實現 run 方法。然後可以分配該類的例項,在建立 Thread 時作為一個引數來傳遞並啟動。

  怎麼還要實現Runnable介面,Runable是啥玩意呢?繼續API搜尋。

  檢視Runnable介面說明文件:Runnable 介面應該由那些打算通過某一執行緒執行其例項的類來實現。類必須定義一個稱為 run 的無引數方法。

  總結:

  建立執行緒的第二種方式:實現Runnable介面。

   1、定義類實現Runnable介面。

   2、覆蓋介面中的run方法。。

   3、建立Thread類的物件

   4、將Runnable介面的子類物件作為引數傳遞給Thread類的建構函式。

   5、呼叫Thread類的start方法開啟執行緒。

程式碼演示:
class Demo implements Runnable
{
    private String name;
    Demo(String name)
    {
        this.name = name;
    }
    //覆蓋了介面Runnable中的run方法。
    public void run()
    {
        for(int i=1; i<=20; i++)
        {            System.out.println("name="+name+"..."+Thread.currentThread().getName()+"..."+i);
        }
    }
}
class ThreadDemo2 
{
    public static void main(String[] args) 
    {
        //建立Runnable子類的物件。注意它並不是執行緒物件。
        Demo d = new Demo("Demo");
        //建立Thread類的物件,將Runnable介面的子類物件作為引數傳遞給Thread類的建構函式。
        Thread t1 = new Thread(d);
        Thread t2 = new Thread(d);
        //將執行緒啟動。
        t1.start();
        t2.start();
        System.out.println(Thread.currentThread().getName()+"----->");
        System.out.println("Hello World!");
    }
}
複製程式碼

  輸出結果:

java基礎(五):談談java中的多執行緒

1.7.2.實現Runnable的原理

  為什麼需要定一個類去實現Runnable介面呢?繼承Thread類和實現Runnable介面有啥區別呢?

  實現Runnable介面,避免了繼承Thread類的單繼承侷限性。覆蓋Runnable介面中的run方法,將執行緒任務程式碼定義到run方法中。建立Thread類的物件,只有建立Thread類的物件才可以建立執行緒。執行緒任務已被封裝到Runnable介面的run方法中,而這個run方法所屬於Runnable介面的子類物件,所以將這個子類物件作為引數傳遞給Thread的建構函式,這樣,執行緒物件建立時就可以明確要執行的執行緒的任務。

1.7.3.實現Runnable的好處

  第二種方式實現Runnable介面避免了單繼承的侷限性,所以較為常用。實現Runnable介面的方式,更加的符合物件導向,執行緒分為兩部分,一部分執行緒物件,一部分執行緒任務。繼承Thread類,執行緒物件和執行緒任務耦合在一起。一旦建立Thread類的子類物件,既是執行緒物件,有又有執行緒任務。實現runnable介面,將執行緒任務單獨分離出來封裝成物件,型別就是Runnable介面型別。Runnable介面對執行緒物件和執行緒任務進行解耦。

1.8.執行緒狀態圖

  查閱API關於IllegalThreadStateException這個異常說明資訊發現,這個異常的描述資訊為:指示執行緒沒有處於請求操作所要求的適當狀態時丟擲的異常。這裡面說適當的狀態,啥意思呢?難道是說執行緒還有狀態嗎?

java基礎(五):談談java中的多執行緒

  1、新建(new):執行緒物件被建立後就進入了新建狀態。如:Thread thread = new Thread();  

  2、就緒狀態(Runnable):也被稱為“可執行狀態”。執行緒物件被建立後,其他執行緒呼叫了該物件的start()方法,從而啟動該執行緒。如:thread.start(); 處於就緒狀態的執行緒隨時可能被CPU排程執行。

  3、執行狀態(Running):執行緒獲取CPU許可權進行執行。需要注意的是,執行緒只能從就緒狀態進入到執行狀態。

  4、阻塞狀態(Blocked):阻塞狀態是執行緒因為某種原因放棄CPU使用許可權,暫時停止執行。直到執行緒進入就緒狀態,才有機會進入執行狀態。阻塞的三種情況:

  • 等待阻塞:通過呼叫執行緒的wait()方法,讓執行緒等待某工作的完成。
  • 同步阻塞:執行緒在獲取synchronized同步鎖失敗(因為鎖被其他執行緒佔用),它會進入同步阻塞狀態。
  • 其他阻塞:通過呼叫執行緒的sleep()或join()或發出了I/O請求時,執行緒會進入到阻塞狀態。當sleep()狀態超時、join()等待執行緒終止或超時、或者I/O處理完畢時,執行緒重新轉入就緒狀態。

  5、死亡狀態(Dead):執行緒執行完了或因異常退出了run()方法,該執行緒結束生命週期。

1.8.1.sleep,wait,yield,join的區別

  • sleep()方法

    在指定時間內讓當前正在執行的執行緒暫停執行,但不會釋放“鎖標誌”。不推薦使用。 sleep()使當前執行緒進入阻塞狀態,在指定時間內不會執行。

  • wait()方法

    在其他執行緒呼叫物件的notify或notifyAll方法前,導致當前執行緒等待。執行緒會釋放掉它所佔有的“鎖標誌”,從而使別的執行緒有機會搶佔該鎖。 當前執行緒必須擁有當前物件鎖。如果當前執行緒不是此鎖的擁有者,會丟擲IllegalMonitorStateException異常。 喚醒當前物件鎖的等待執行緒使用notify或notifyAll方法,也必須擁有相同的物件鎖,否則也會丟擲IllegalMonitorStateException異常。 waite()和notify()必須在synchronized函式或synchronized block中進行呼叫。如果在non-synchronized函式或non-synchronized block中進行呼叫,雖然能編譯通過,但在執行時會發生IllegalMonitorStateException的異常。

  • yield方法

    暫停當前正在執行的執行緒物件。 yield()只是使當前執行緒重新回到可執行狀態,所以執行yield()的執行緒有可能在進入到可執行狀態後馬上又被執行。 yield()只能使同優先順序或更高優先順序的執行緒有執行的機會。 呼叫yield方法並不會讓執行緒進入阻塞狀態,而是讓執行緒重回就緒狀態,它只需要等待重新獲取CPU執行時間,這一點是和sleep方法不一樣的。

  • join方法

    等待該執行緒終止。 等待呼叫join方法的執行緒結束,再繼續執行。如:t.join();//主要用於等待t執行緒執行結束,若無此句,main則會執行完畢,導致結果不可預測

1.9.執行緒的安全問題

  帶女朋友看電影,需要買票,電影院要賣票,模擬電影院的買票操作

  假設我們想要的電影是 “功夫熊貓3”,本次電影的座位共100個(本廠電影只能賣100張票)

  模擬電影院的售票視窗,實現多個視窗同時賣 “功夫熊貓3”這場電影票(多個視窗一起賣這100張票)

  需要視窗:採用執行緒物件

  需要票:Runnable介面子類來模擬

public class ThreadDemo {
    public static void main(String[] args) {
        //建立票物件
        Ticket ticket = new Ticket();
        
        //建立3個視窗
        Thread t1  = new Thread(ticket, "視窗1");
        Thread t2  = new Thread(ticket, "視窗2");
        Thread t3  = new Thread(ticket, "視窗3");
        
        t1.start();
        t2.start();
        t3.start();
    }
}

public class Ticket implements Runnable {
    //共100票
    int ticket = 100;

    @Override
    public void run() {
        //模擬賣票
        while(true){
            //t1,t2,t3
            if (ticket > 0) {
                //模擬選坐的操作
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在賣票:" + ticket--);
            }
        }
    }
}
複製程式碼

java基礎(五):談談java中的多執行緒
  總結:上面程式出新了問題

   票出現了重複的票

   錯誤的票 0

1.10.同步的鎖

  • 同步程式碼塊: 在程式碼塊宣告上 加上synchronized
synchronized (鎖物件) {     
    可能會產生執行緒安全問題的程式碼
}
複製程式碼

  同步程式碼塊中的鎖物件可以是任意的物件,多個執行緒物件使用的是同一個鎖物件

  把Ticket.java進行了程式碼修改

public class Ticket implements Runnable {
    //共100票
    int ticket = 100;
    
    //定義所物件
    Object lock = new Object();
    @Override
    public void run() {
        //模擬賣票
        while(true){
            //同步程式碼塊
            synchronized (lock){
                if (ticket > 0) {
                    //模擬選坐的操作
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在賣票:" + ticket--);
                }
            }
        }
    }
}
複製程式碼

  當使用了同步程式碼塊後,上述的執行緒的安全問題,解決了。

  • 同步方法:在方法宣告上加上synchronized
 public synchronized void method(){
 
     可能會產生執行緒安全問題的程式碼
 
 }
複製程式碼

  同步方法中的鎖物件是 this

//同步方法,鎖物件this
    public synchronized void method(){
        //this.name = name;
        if (ticket > 0) {
            //模擬選坐的操作
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在賣票:" + ticket--);
        }
    }
複製程式碼
  • 靜態同步方法: 在方法宣告上加上synchronized
 public static synchronized void method(){
 可能會產生執行緒安全問題的程式碼
 }
複製程式碼

1.11.死鎖

  同步鎖的另一個弊端:當執行緒任務中出現了多個同步(多個鎖)時,如果同步中巢狀了其他的同步。這時容易引發一種現象:死鎖。這種情況能避免就避免掉。

 synchronzied(A鎖){
     synchronized(B鎖){
          
 }
 }
複製程式碼
/*
 * 定義鎖物件
 */
public class MyLock {
    public static final Object lockA = new Object();
    public static final Object lockB = new Object();
}

/*
 * 執行緒任務類
 */
public class ThreadTask implements Runnable {

    int x = new Random().nextInt(1);//0,1    
    
    //指定執行緒要執行的任務程式碼
    @Override
    public void run() {
        while(true){
            if (x%2 ==0) {
                //情況一
                synchronized (MyLock.lockA) {
                    System.out.println("if-LockA");
                    synchronized (MyLock.lockB) {
                        System.out.println("if-LockB");
                        System.out.println("if大口吃肉");
                    }
                }
            } else {
                //情況二
                synchronized (MyLock.lockB) {
                    System.out.println("else-LockB");
                    synchronized (MyLock.lockA) {
                        System.out.println("else-LockA");
                        System.out.println("else大口吃肉");
                    }
                }
            }
            x++;
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        //建立執行緒任務類物件
        ThreadTask task = new ThreadTask();
        
        //建立兩個執行緒
        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);
        
        //啟動執行緒
        t1.start();
        t2.start();
    }
}
複製程式碼

1.12.Lock介面

  查閱API,發現Lock介面,比同步更厲害,有更多操作;

   lock():獲取鎖

   unlock():釋放鎖;

  提供了一個更加面對物件的鎖,在該鎖中提供了更多的顯示的鎖操作。使用Lock介面,以及其中的lock()方法和unlock()方法替代同步。

  如下程式碼演示:

public class Ticket implements Runnable {
    //共100票
    int ticket = 100;
    
    //建立Lock鎖物件
    Lock ck = new ReentrantLock();
    
    @Override
    public void run() {
        //模擬賣票
        while(true){
            //synchronized (lock){
            ck.lock();
                if (ticket > 0) {
                    //模擬選坐的操作
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在賣票:" + ticket--);
                }
            ck.unlock();
            //}
        }
    }
}
複製程式碼

1.13.執行緒的匿名內部類的使用

方式1
        new Thread() {
            public void run() {
                for (int x = 0; x < 40; x++) {
                    System.out.println(Thread.currentThread().getName()
                            + "...X...." + x);
                }
            }
        }.start();

方式2
        Runnable r = new Runnable() {
            public void run() {
                for (int x = 0; x < 40; x++) {
                    System.out.println(Thread.currentThread().getName()
                            + "...Y...." + x);
                }
            }
        };
        new Thread(r).start();
複製程式碼

2.多執行緒檔案上傳

  實現伺服器端可以同時接收多個客戶端上傳的檔案。 我們要修改伺服器端程式碼

/*
 * 檔案上傳  伺服器端
 *
 */
public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1,建立伺服器,等待客戶端連線
        ServerSocket serverSocket = new ServerSocket(6666);
        
        //實現多個客戶端連線伺服器的操作
        while(true){
            final Socket clientSocket = serverSocket.accept();
            //啟動執行緒,完成與當前客戶端的資料互動過程
            new Thread(){
                public void run() {
                    try{
                        //顯示哪個客戶端Socket連線上了伺服器
                        InetAddress ipObject = clientSocket.getInetAddress();//得到IP地址物件
                        String ip = ipObject.getHostAddress(); //得到IP地址字串
                        System.out.println("小樣,抓到你了,連線我!!" + "IP:" + ip);
                        
                        //7,獲取Socket的輸入流
                        InputStream in = clientSocket.getInputStream();
                        //8,建立目的地的位元組輸出流   D:\\upload\\192.168.74.58(1).jpg
                        BufferedOutputStream fileOut = new BufferedOutputStream(new FileOutputStream("D:\\upload\\"+ip+"("+System.currentTimeMillis()+").jpg"));
                        //9,把Socket輸入流中的資料,寫入目的地的位元組輸出流中
                        byte[] buffer = new byte[1024];
                        int len = -1;
                        while((len = in.read(buffer)) != -1){
                            //寫入目的地的位元組輸出流中
                            fileOut.write(buffer, 0, len);
                        }
                        
                        //-----------------反饋資訊---------------------
                        //10,獲取Socket的輸出流, 作用:寫反饋資訊給客戶端
                        OutputStream out = clientSocket.getOutputStream();
                        //11,寫反饋資訊給客戶端
                        out.write("圖片上傳成功".getBytes());
                        
                        out.close();
                        fileOut.close();
                        in.close();
                        clientSocket.close();
                    } catch(IOException e){
                        e.printStackTrace();
                    }
                };
            }.start();
        }

        //serverSocket.close();
    }
}
複製程式碼

3.總結

  • 建立執行緒的方式

  方式1,繼承Thread執行緒類

  步驟

    1, 自定義類繼承Thread類

    2, 在自定義類中重寫Thread類的run方法

    3, 建立自定義類物件(執行緒物件)

    4, 呼叫start方法,啟動執行緒,通過JVM,呼叫執行緒中的run方法

   方式2,實現Runnable介面

   步驟

    1, 建立執行緒任務類 實現Runnable介面

    2, 線上程任務類中 重寫介面中的run方法

    3, 建立執行緒任務類物件

    4, 建立執行緒物件,把執行緒任務類物件作為Thread類構造方法的引數使用

    5, 呼叫start方法,啟動執行緒,通過JVM,呼叫執行緒任務類中的run方法

  • 同步鎖

  多個執行緒想保證執行緒安全,必須要使用同一個鎖物件

A.同步程式碼塊

  synchronized (鎖物件){
     可能產生執行緒安全問題的程式碼
 }
複製程式碼

同步程式碼塊的鎖物件可以是任意的物件

  

B.同步方法

   public synchronized void method()
               可能產生執行緒安全問題的程式碼
 }
複製程式碼

同步方法中的鎖物件是 this

C.靜態同步方法

 public synchronized static void method()
               可能產生執行緒安全問題的程式碼
 }
複製程式碼

靜態同步方法中的鎖物件是 類名.class

參考:

java程式設計思想

黑馬教學視訊

www.cnblogs.com/huangzhe151…

blog.csdn.net/qq_38545713…

www.cnblogs.com/dz-boss/p/1… (個人部落格)

相關文章