java學習筆記:多線程

java學習筆記:多線程

 

java多線程
----
一、基本概念介紹
1.多線程就是指一個應用程序中有多條併發執行的路徑,每條路徑都被稱爲一個線程,它們會交替執行,彼此間可以進行通信.
2.併發 concureent:一段時間內,多個線程同時運行。(CPU的高速切換)
3.進程:在一個操作系統中,每個獨立執行的程序都可稱爲一個進程,也就是 `正在運行的程序`.
4.線程:線程是運行在進程中的 `小進程`.
進程 :Window系統自動分配.
線程 :程序代碼進行分配.
5.問題 :線程是不是越多越好呢?對於一個CPU而言,同一時刻,只能執行一個任務。同一時間段內,如果線程過多,每個線程被切到的時間就變少了。因此,線程並不是越多越好。

二、線程的創建
1.請問 :如何創建並啓動一個線程呢?
Java提供了兩種多線程實現的方式.
1_繼承java.lang包下的Thread類,重寫Thread類的run方法.在run()方法中實現運行在線程上的代碼.
2_實現java.lang.Runnable接口,同樣是在run()方法中實現運行在線程上的代碼.

2.方式一:繼承Thread
java.lang.Thread
(1)併發地運行多個執行線程第一種方法是:將類聲明爲Thread的子類。該子類應重寫Thread類的run方法。接下來可以分配並啓動該子類的實例。
(2)實現步驟及演示
a.步驟
1_自定義一個類,繼承Thread。這個類稱爲線程類。
2_重寫run方法
3_創建線程類的對象
4_啓動線程void start()//使該線程開始執行。jvm調用該線程的run方法。
代碼實現:
//創建線程實現方式一:繼承Thread類
//file name: Test03.java
public class Test03 extends Thread {
    //屬性
    private String name;

    public Test03(String name){
        this.name = name;
    }

    //行爲
    @Override
    public void run(){
        while(true){
            System.out.println(name + " run ...");
        }
    }
}

//file name: HelloWorld.java
public class HelloWorld {
    public static void main(String[] args) {
        //1.創建一個Test03類的對象
        Test03 t1 = new Test03("one");
        t1.start();

        //2. 再創建一個Test03類的對象
        Test03 t2 = new Test03("two");
        t2.start();

        //3. main線程代碼
        while(true){
            System.out.println("main run ...");
        }
    }
}


(3)面試題:start方法和run方法的區別?
run:封裝線程 `任務`. 不調用系統資源,開闢新線程.
start:先調用系統資源,啓動線程,再執行run方法。

(4)獲取和設置線程名稱
Thread類的三個與線程名稱相關的方法:
String getName()//返回該線程的名稱。
void setName(String name)//改變線程名稱,使之與參數那麼相同。
static Thread currentThread()//返回對當前正在執行的線程對象的引用。

代碼示例:
//file name: Test05.java
/**
 * 獲取線程名稱:
 *   方式一:線程子類中通過getName()方法
 *   方式二:非線程子類中通過Thread.currentThread().getName()
 *
 *   結論:
 *     主線程的默認名稱爲:main
 *     自定義線程的格式:Thread-N, N從0開始,依次遞增
 * 設置線程名稱:
 *   方式一:線程類對象.setName(String name)
 *   方式二:構造方法 new Thread(String name)
 * */
public class Test05 extends Thread {
    public Test05(String name){
        super(name);
    }

    @Override
    public void run(){
        for(int i = 1; i <= 10; i++){
            System.out.println(this.getName() + " ==> run ... " + i);
        }
    }
}

//file name: Demo05.java
public class Demo05 {
    public static void main(String[] args) {
        Thread t1 = new Test05("自定義線程一");
        Thread t2 = new Test05("自定義線程二");

        //設置線程名稱
//        t1.setName("線程一");
//        t2.setName("線程二");

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

//        Thread.currentThread().setName("主線程");
        for(int i = 1; i <= 10; i++){
            System.out.println(Thread.currentThread().getName() + " ==> run ... " + i);
        }
    }
}

總結 :線程有默認的名字 : Thread-n, n是從0開始遞增的數字.
在程序中當某個線程發生了異常,那麼當前這個線程就會自動停止運行,這個線程就自動的結束。只要其他線程沒有發生問題,依然會正常的運行。直到線程正常的把run方法中的所有代碼執行完成,這個線程纔會結束。

(5)繼承Thread優點小結 :
使用 Thread子類創建線程的優點是:可以在子類中增加新的成員變量,使線程具有某種屬性,也可以在子類中增加新的方法,使線程具有某種功能.但是,Java不支持多繼承,Thread類的子類不能在擴展其他的類.

(6)線程的運行狀態圖(生命週期)
見圖:線程的運行狀態圖(生命週期).png


3.方式二:實現Runnable接口
說明 :創建線程的另一個途徑就是用Thread類直接創建線程對象.使用Thread創建線程通常使用的構造方法是:Thread(Runnabletarget);該構造方法中的參數是一個 Runnable類型的接口.因此,在創建線程對象時必須向構造方法的參數傳遞一個實現 Runnable接口類的實現類對象.線程綁定 Runnable接口,也就是說,當線程被調度並轉入運行狀態時,所執行的就是 run()方法中所規定的操作.(接口回調)
(1)Runnable接口介紹
java.lang
public interface Runnable
Runnable接口應該由那些打算通過某一線程執行其實例的類來實現。類必須定義一個稱爲run的五參數方法。
方法摘要
void run()//使用實現接口Runnable的對象創建一個線程時,啓動該線程將導致在獨立執行的線程中調用對象的run方法。

我們發現,Thread類中的run方法就是實現自Runnable接口。
run方法是用來封裝線程任務的。
Runnable接口中,只有一個run方法,因此,這個接口就是專門用來封裝線程任務的接口。
因此,實現該接口的類,稱爲線程任務類。
觀察 :Thread類中的run()方法就是實現了 Runnable接口中的run()方法.

(2)實現步驟
1_自定義類,實現Runnable接口,這個類就是任務類
2_實現接口中的run方法
3_創建任務類的對象
4_創建Thread類對象,並且把任務對象作爲參數傳遞
5_啓動線程

Thread(Runnable target)//分配新的Thread對象。
Thread(Runnable target, String name)//分配新的Thread對象。

代碼示例:
//file name: TestTask06.java
/**
 * 1.創建任務類實現Runnable接口
 * 2.重寫run方法
 * */
public class TestTask06 implements Runnable {
    @Override
    public void run(){
        for(int i = 1; i <= 10; i++){
            System.out.println(Thread.currentThread().getName() + " ==> run ... " + i);
        }
    }
}

//file name: Demo06.java
/**
 * 創建線程的方式二:
 *   1.創建任務類實現Runnable接口
 *   2.重寫run方法
 *   3.創建任務類
 *   4.創建線程類對象,將任務類對象作爲參數傳給線程類對象
 *   5.啓動線程
 * */
public class Demo06 {
    public static void main(String[] args) {
        //3.創建任務類
        Runnable task = new TestTask06();
        //4.創建線程類對象,將任務類對象作爲參數傳給線程類對象
        Thread t1 = new Thread(task, "線程一");
        Thread t2 = new Thread(task, "線程二");
        //5.啓動線程
        t1.start();
        t2.start();

        for(int i = 0; i <= 10; i++){
            System.out.println(Thread.currentThread().getName() + " ==> run ... " + i);
        }
    }
}


(3)實現原理
方式1:自定義類繼承Thread類,重寫run方法,調用start啓動線程,會自動調用我們線程類中的run方法。
方式2:自定義類,實現Runnable接口,把任務對象傳給Thread對象。調用Thread對象的start方法,執行Thread的run。那麼爲什麼最後執行的是任務類中的run呢?

分析 : 
1_在我們創建 Thread 類對象時,我們已經將 task 任務類的對象作爲參數傳遞給了線程類對象.在其內容就會將 task 賦值給內部屬性 target 進行存儲.
2_當我們調用 Thread 對象的 start 方法啓動線程時,肯定會執行 Thread 類的 run 方法.而 run 方法的實現如下 :
@Override
public void run(){
  if(target != null){
    target.run();
  }
}
3_在 Thread 的 run 方法中, 會先判斷 target 是否爲 null. 這個target就是我們創建 Thread 對象時傳入的任務類對象,所以 target 不爲 null, 因此就會執行 target 的 run 方法. 也就是任務類的 run 方法.


二、兩種實現多線程方式的對比分析
請問 :既然直接繼承Thread類和實現Runnable接口都能實現多線程,那麼這兩種實現多線程的方式在實際應用中又有什麼區別呢 ?
需求 :假設某航空公司有三個窗口發售某日某次航班的100張票,這時,100張票可以作爲共享資源,三個售票窗口需要創建三個線程.使用代碼模擬實現.
注意:不是每個售票點都賣100張票,是一共賣了100張票.
方式1 :繼承Thread類
package cn.itcast.b_demo;

publicclass TicketWindowDemo01 {
    publicstaticvoid main(String[] args) {
        // 1 開啓三個窗口賣票
        TicketWindow01 t1 = new TicketWindow01("窗口1");
        TicketWindow01 t2 = new TicketWindow01("窗口2");
        TicketWindow01 t3 = new TicketWindow01("窗口3");
        
        // 2 啓動賣票
        t1.start();
        t2.start();
        t3.start();
    }
}

class TicketWindow01 extends Thread {
    private  int tickets = 100;
    
    public TicketWindow01(String name){
        super(name);
    }
    
    @Override
    publicvoid run() {
        // 不斷賣票
        while(true) {
            if(tickets>0) {
                System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "張票!");
                tickets = tickets -1;
            } else {
                // 賣完爲止
                break;
            }
        }
    }
}

分析 :從運行結果可以看出,每張票都被打印了三次,出現這種現象的原因是三個線程沒有共享100張票,而是各自出售了100張票,在程序中共創建了三個TicketWindow對象,就等於創建了三個售票程序,每個程序都有自己的100張票,每個線程在獨立的處理各自的資源.

方式2 :實現 Runnable接口
說明 :由於現實航空系統中的票資源是共享的,因此上面的運行接口顯然不合理,爲了保證資源共享,在程序中只能創建一個售票對象,然後開啓多個線程去運行同一個售票對象的售票方法,簡單來說就是三個線程運行同一個售票程序,這時就需要用到多線程的第二種實現方式.
package cn.itcast.b_demo;

publicclass Demo02 {
    publicstaticvoid main(String[] args) {
        // 1 創建任務類
        TicketWindowTask task = new TicketWindowTask();

        // 2 創建線程
        Thread t1 = new Thread(task, "窗口1");
        Thread t2 = new Thread(task, "窗口2");
        Thread t3 = new Thread(task, "窗口3");

        // 3 啓動線程
        t1.start();
        t2.start();
        t3.start();
    }
}

class TicketWindowTask implements Runnable {

    privateinttickets = 100;
    
    @Override
    publicvoid run() {
        // 同步代碼塊
        // 不斷賣票
        while (true) {
            if (tickets> 0) {
                // 整數-- : 操作1 使用整數; 操作2將整數-1.
                System.out.println(Thread.currentThread().getName() + "正在賣第" + tickets-- + "張票!");
            } else {
                break;
            }
        }
    }
}


使用接口完成多線程的好處
好處一:避免了Java單繼承帶來的侷限性
好處二:適合多個相同程序代碼的線程去處理同一個資源的情況,更靈活的實現數據的共享。


三、線程同步–問題引出
說明 :多線程的併發執行可以提高程序的效率,但是,當多個線程去訪問同一個資源時,也會引發一些安全問題.我們必須注意這樣一個問題,當兩個或多個線程同時訪問同一個變量,並且一些線程需要修改這個變量,程序應對這樣的問題作出處理,否則可能發生混亂.
1.觀察之前的售票系統
說明 :在之前的售票案例中,極有可能碰到 `意外`情況,如一張票被打印多次,或者打印出的票號爲0,甚至負數.這些 `意外`都是由多線程操作共享資源tickets所導致的線程安全問題.
package cn.itcast.b_demo;

publicclass Demo02 {
    publicstaticvoid main(String[] args) {
        // 1 創建任務類
        TicketWindowTask task = new TicketWindowTask();

        // 2 創建線程
        Thread t1 = new Thread(task, "窗口1");
        Thread t2 = new Thread(task, "窗口2");
        Thread t3 = new Thread(task, "窗口3");

        // 3 啓動線程
        t1.start();
        t2.start();
        t3.start();
    }
}

/**
 * 優點:
 *         1 避免了單繼承的缺點
 *         2 方便操作相同代碼的線程完成
 */
class TicketWindowTask implements Runnable {

    privateinttickets = 100;
    
    @Override
    publicvoid run() {
        // 同步代碼塊
        // 不斷賣票
        while (true) {
            if (tickets> 0) {
                // 模擬耗時操作
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 整數-- : 操作1 使用整數; 操作2 將整數-1.
                System.out.println(Thread.currentThread().getName() + "正在賣第" + tickets-- + "張票!");
            } else {
                break;
            }
        }
    }
}

原因分析:多個線程操作共享資源.CPU高速切換.
上述錯誤的編號,是因爲有多個線程,並且多個線程同時在操作同一個變量。

出現的錯誤票號:
是多個線程在執行售票的任務的時候,由於在售票的代碼中訪問了同一個成員變量tickets。可是在操作tickets的這些語句中,一個線程操作到其中的一部分代碼的時候,CPU切換到其他的線程上開始執行代碼。這樣就會導致tickets變量中的值被修改的不一致。
上述的這些錯誤,我們稱爲多線程運行的安全問題。

總結多線程的安全問題發生的原因:
1_首先必須有多線程。
2_多個線程在操作共享的數據,並且對共享數據有修改。
3_本質原因是CPU在處理多個線程的時候,在操作共享數據的多條代碼之間進行切換導致的。


2.多線程安全問題解決
(1)同步代碼塊
說明 :我們瞭解到線程的安全問題其實就是由多個線程同時處理共享資源所導致的,要想解決線程的安全問題,必須保證下面用於處理共享資源的代碼在任何時刻只能被一個線程訪問.
//判斷是否還有餘額
if(tickets > 0){
  try{
    Thread.sleep(10);
  }catch(InterruptedException e){
    e.printStackTrace();
  }
  System.out.println(Thread.currentThread().getName() + " 正在出售第 " + tickets-- + " 張票。");
}else{
  break;
}

爲了實現這種限制,Java中提供了同步機制,當多個線程使用同一個共享資源時,可以將處理共享資源的代碼放置在一個代碼塊中,使用Synchronized關鍵字來修飾.被稱作同步代碼塊.
synchronized(鎖對象){
  //操作共享資源代碼塊
}

問題1:鎖對象是什麼?任意一個對象.
問題2:哪些代碼需要被同步??操作資源的所有代碼
上述的這個解決方案:稱爲線程的同步。

要想保證線程的安全:需要在操作共享數據的地方,加上線程的同步鎖。
鎖對象的前提條件 :必須要保證 `鎖對象`的唯一性.

注意 :同步代碼塊中的鎖對象可以是任意類型的對象,但多個線程共享的鎖對象必須是唯一的. `任意` 說的是共享鎖對象的類型, 所以, 鎖對象的創建代碼不能放在 run() 方法中.否則每個線程運行到 run()方法都會創建一個新對象,這樣每個線程都會有一個不同的鎖, 每個鎖都有自己的標誌位. 線程之間便不能產生同步的效果.

package cn.itcast.b_demo;

publicclass Demo02 {
    publicstaticvoidmain(String[] args) {
        // 1 創建任務類
        TicketWindowTask task = new TicketWindowTask();

        // 2 創建線程
        Thread t1 = new Thread(task, "窗口1");
        Thread t2 = new Thread(task, "窗口2");
        Thread t3 = new Thread(task, "窗口3");

        // 3 啓動線程
        t1.start();
        t2.start();
        t3.start();
    }
}

/**
 * 優點: 1 避免了單繼承的缺點 2 方便操作相同代碼的線程完成
 */
class TicketWindowTask implements Runnable {

    privateinttickets = 100;

    // 定義一把鎖
    private Object obj = new Object();

    @Override
    publicvoid run() {
        // 同步代碼塊
        // 不斷賣票
        while (true) {
            synchronized (obj) {
                if (tickets> 0) {
                    // 模擬耗時操作
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 整數-- : 操作1 使用整數; 操作2 將整數-1.
                    System.out.println(Thread.currentThread().getName() + "正在賣第" + tickets-- + "張票!");
                } else {
                    break;
                }
            }
        }
    }
}

模擬結果 : 出票規律,順序完全正確!

注意事項 :
A:同步代碼塊中,只能包含操作資源的代碼。不能亂包。
B:同步代碼塊的鎖可以是任意的。但是必須是唯一的。

補充說明 :lock是一個鎖對象,它是同步代碼塊的關鍵.當線程執行同步代碼塊時,首先會檢查所鎖象的標誌位,默認情況下標誌位爲1,此時線程會執行同步代碼塊,同時將鎖對象的標誌位設置爲0.當一個新的線程執行到這段同步代碼塊時,由於鎖對象的標誌位爲0,新線程會發生阻塞,等待當前線程執行完同步代碼塊後,鎖對象的標誌位設置1,新線程才能進入同步代碼塊執行其中的代碼,循環往復,直到共享資源被處理完爲止.這個過程就好比一個`公用電話亭`,只有前一個人打完電話出來後,後面的人才可以進入電話亭撥打電話.


3.關於 this作爲鎖對象的演示與說明 :
//file name: Demo02.java
public class Demo02 {
    public static void main(String[] args) {
        //1.創建任務類
        TicketWindowTask task = new TicketWindowTask();

        //2.創建線程
        Thread t1 = new Thread(task, "窗口1");
        Thread t2 = new Thread(task, "窗口2");
        Thread t3 = new Thread(task, "窗口3");

        //3.啓動線程
        t1.start();
        t2.start();
        t3.start();
    }
}


//file name: TicketWindowTask.java
/**
 * 優點:1.避免了單繼承的缺點。2.方便操作相同代碼的線程完成。
 * */
public class TicketWindowTask implements Runnable {
    private int tickets = 100;

    @Override
    public void run(){
        //同步代碼塊
        //不斷賣票
        while(true){
            //this掉膘調用方法的當前對象
            synchronized(this){
                if(tickets > 0){
                    //模擬耗時操作
                    try{
                        Thread.sleep(10);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                    //整數--:操作1.使用整數;操作2.將整數-1。
                    System.out.println(Thread.currentThread().getName() + "正在賣第" + tickets-- + "張票!");
                }else{
                    break;
                }
            }
        }
    }
}


4.同步方法
說明 :通過同步代碼塊我們瞭解到可以有效解決線程的安全問題,當把共享資源的操作放在Synchronized定義的區域內時,便爲這些操作加了同步鎖.
同步方法 :在方法前面同樣可以使用Synchronized關鍵字來修飾,被修飾的方法稱爲 `同步方法`,它能實現和同步代碼塊同樣的功能.
修飾符synchronized 返回值類型 方法名(參數列表){}
被synchronized修飾的方法在某一時刻只允許一個線程訪問,訪問該方法的其它線程都會發生阻塞,直到當前線程訪問完畢後,其它線程纔有機會執行方法.
代碼:
//file name: TicketWindowTask03.java
public class TicketWindowTask03 implements Runnable {
    private int tickets = 100;
    @Override
    public void run() {
        //同步代碼塊
        //不斷賣票
        while(true){
            //this代表調用方法的當前對象
            sellTicket();
            if(tickets <= 0){
                break;
            }
        }
    }

    //如果一個方法的代碼都需要同步,就可以直接使用“同步方法”直接實現。
    //問題一:同步方法有沒有鎖
    //問題而:同步方法的鎖是誰?this
    public synchronized void sellTicket(){
        if(tickets > 0){
            //模擬耗時操作
            try{
                Thread.sleep(10);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            //整數--:操作1.使用整數;操作2:將整數-1。
            System.out.println(Thread.currentThread().getName() + "正在賣第" + tickets + "張票!");
        }
    }
}


//file name: Demo03.java
public class Demo03 {
    public static void main(String[] args) {
        //1.創建任務類
        TicketWindowTask03 task = new TicketWindowTask03();

        //2.創建線程
        Thread t1 = new Thread(task, "窗口1");
        Thread t2 = new Thread(task, "窗口2");
        Thread t3 = new Thread(task, "窗口3");

        //3.啓動線程
        t1.start();
        t2.start();
        t3.start();
    }
}

思考 : 大家可能會有這樣的疑問 : 同步代碼塊的鎖是自己定義的任意類型的對象, 那麼同步方法是否也存在鎖? 如果有, 它的鎖是什麼呢?
答案是肯定的, 同步方法也有鎖, 它的鎖就是當前調用該方法的對象, 也就是this指向的對象.
這樣做的好處是, 同步方法被所有線程所共享, 方法所在的對象相對於所有線程來說是唯一的, 從而保證了鎖的唯一性. 當一個線程執行該方法時, 其它的線程就不能進入到該方法中, 直到這個線程執行完該方法爲止, 從而達到了線程同步的效果.

注意:
    1:如果一個方法內部,所有代碼都需要被同步,那麼就用同步方法
    2:同步方法的鎖是this


四、死鎖問題
1.什麼是死鎖
死鎖:是指兩個或者兩個以上的線程在執行的過程中,因爭奪資源產生的一種互相等待現象
有這樣一個場景 : 一箇中國人和一個美國人在一起喫飯, 美國人拿了中國人的筷子, 中國人拿了美國人的刀叉, 兩個人開始爭執不休:
中國人 : “你先給我筷子, 我再給你刀叉”.
美國人 : “你先給我刀叉, 我再給你筷子”.
……
結果可想而知, 兩個人都喫不到飯, 這個例子中的中國人和美國人相當於不同的線程, 筷子和刀叉就相當於鎖, 兩個線程線程在運行時都在等待對方的鎖,這樣便造成了程序的停滯,這種現象稱爲死鎖.

2.代碼演示
核心 :同步鎖中嵌套同步鎖.
//file name: Test08.java
public class Test08 implements Runnable{

    //區分中國人和美國人
    boolean flag = true;//true:表示中國人; false:表示美國人
    Object daochaLock = new Object();//刀叉鎖
    Object kuaiziLock = new Object();//筷子鎖

    @Override
    public void run(){
        if(flag == true){
            while(true){
                //中國人線程
                synchronized(kuaiziLock){
                    System.out.println("----中國人  1  正在使用筷子鎖----");
                    try{
                        Thread.sleep(10);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                    synchronized(daochaLock){
                        System.out.println("----中國人  2  正在使用刀叉鎖----");
                    }
                }
            }
        }else{
            while(true){
                //美國人線程
                synchronized (daochaLock){
                    System.out.println("----美國人  1  正在使用刀叉鎖----");
                    try{
                        Thread.sleep(10);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                    synchronized (kuaiziLock){
                        System.out.println("----美國人  2  正在使用筷子鎖----");
                    }
                }
            }
        }
    }
}


//file name: Demo08.java
public class Demo08 {
    public static void main(String[] args) {
        //演示死鎖
        Test08 task = new Test08();

        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);

        t1.start();
        try{
            Thread.sleep(10);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        task.flag = false;
        t2.start();
    }
}

說明 : 以上創建了 Chinese 和 American 兩個線程, 分別執行 run() 方法中 if 和 else 代碼塊中的同步代碼塊, Chinese 線程中擁有 Chopsticks 鎖, 只有獲得 knifeAndFork 鎖才能執行完畢, 而America 線程擁有 knifeAndFork 鎖, 只有獲得 chopsticks 鎖才能執行完畢, 兩個線程都需要對方所佔用的鎖, 但是都無法釋放自己所擁有的鎖, 於是這兩個線程都處於了掛起狀態, 從而造成了死鎖.


五、匿名內部類方式實現多線程
/**
 * 請問:如何執行?執行誰呢?爲什麼?
 * 問題1:接口的run()方法何時被執行?回答:只有執行Thread類的run()方法時,接口的target.run()纔會執行。
 * 問題2:如果沒有機會執行Thread類的run()方法,那麼接口的run()方法還有機會被執行嗎?回答:沒有。
 * 問題3:如果子類重寫了父類的run()方法,父類(Thread)的run()方法有機會被執行嗎?回答:沒有。
 *
 * 結果:只會執行“多態版本匿名子類”的run()方法。
 * */
public class Demo06 {
    public static void main(String[] args) {
        //1.匿名子類實現方式
        new Thread(){
            @Override
            public void run(){
                for(int i = 1; i <= 10; i++){
                    try{
                        Thread.sleep(10);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " 匿名子類實現方式-->run ... " + i);
                }
            }
        }.start();

        //2.匿名接口實現方式
        new Thread(new Runnable(){
            @Override
            public void run(){
                try{
                    Thread.sleep(10);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
                for(int i = 1; i <= 10; i++){
                    System.out.println(Thread.currentThread().getName() + " 匿名實現類 --> run ... " + i);
                }
            }
        }).start();
    }
}


六、多線程面試題小結 : 
1_ 多線程有幾種實現方案, 分別是哪幾種 ?
第一種 : 繼承 Thread 類
a. 自定義類, 繼承Thread
b. 重寫 run() 方法
c. 創建線程類對象
d. 啓動線程
第二種 : 實現 Runnable 接口
a. 自定義類, 實現Runnable 接口 AA
b. 實現 run() 方法
c. 創建線程任務類對象 AA a=new AA();
d . 創建線程對象, 將任務類對象作爲參數傳入.  Thread t=new Thread(a);
e. 啓動線程

2_ 同步有幾種方式, 分別是什麼 ?
第一種 : 同步代碼塊. 鎖是任意對象, 但是必須保證唯一性.
第二種 : 同步方法. 鎖是 this 當前對象.
其實 : JDK5.0 之後提供了新的一個同步機制 : 
Lock接口
獲取鎖 : lock.lock();
釋放鎖 : lock.unlock();

3_ run() 和 start() 的區別 ?
run : 僅僅是封裝線程任務執行代碼, 不調用系統資源開闢新線程.
start : 先調用系統資源,開闢新線程, 在新線程中執行 run()方法的任務代碼.

4_ 線程的生命週期.
Block阻塞狀態 
New新建狀態    
Runnable就緒狀態    Running運行狀態    Terminated死亡狀態
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章