帶你瞭解多線程

@[toc]java

多線程

1、程序、進程、線程

一、程序

  • 程序(program)是爲完成特定任務、用某種語言編寫的一組指令的集合。即指一段靜態的代碼,靜態對象。

二、進程

  • 是程序的一次執行過程,或是正在運行的一個程序。是一個動態的過程:有它自身的產生、存在和消亡的過程。面試

    1. 進程做爲資源分配的單位,系統在運行時會爲每一個進程分配不一樣的內存區域
    2. 程序是靜態的,進程是動態的

三、線程

  • 進程可進一步細化爲線程,是一個程序內部的一條執行路徑算法

    1. 若一個進程同一時間 並行執行多個線程,就是支持多線程的
    2. 線程做爲調度和執行的單位,每一個線程擁有獨立的運行棧和程序計數器(pc),線程切換的開銷小
    3. 一個進程中的多個線程共享相同的內存單元/內存地址空間---->它們從同一堆中分配對象,能夠訪問相同的變量和對象。這就使得線程間通訊更簡便、高效。但多個線程操做共享的系統資源可能就會帶來安全的隱患。

四、並行與併發

  • 區別:設計模式

    並行:多個CPU同時執行多個任務。好比:多我的同時作不一樣的事。安全

    併發:一個CPU(採用時間片)同時執行多個任務。好比:秒殺、多我的作同一件事。多線程

2、線程的建立和使用

一、Thread類

1.一、Thread類的特性

  • 每一個線程都是經過某個特定Thread對象的run()方法來完成操做的,常常把run()方法的主體稱爲線程體
  • 經過該Thread對象的start()方法來啓動這個線程,而非直接調用run()

1.二、構造器

  • Thread():建立新的Thread對象
  • Thread(String threadname):建立線程並指定線程實例名
  • Thread(Runnable target) :指定建立線程的目標對象,它實現了Runnable接口中的run方法
  • Thread(Runnable target, String name) :建立新的Thread對象

二、建立線程的第一種方式(繼承Thread類)

  • JDK1.5以前建立新執行線程有兩種方法:併發

    繼承Thread類的方式app

    實現Runnable接口的方式ide

  • 繼承Thread類工具

    定義子類繼承Thread類。

    子類中重寫Thread類中的run方法。

    建立Thread子類對象,即建立了線程對象。

    調用線程對象start方法:啓動線程,調用run方法。

  • mt子線程的建立和啓動過程

三、建立線程和使用的注意點

  • 若是本身手動調用run()方法,那麼就只是普通方法,沒有啓動多線程模式
  • run()方法由JVM調用,何時調用,執行的過程控制都有操做系統的CPU調度決定。
  • 想要啓動多線程,必須調用start方法
  • 一個線程對象只能調用一次start()方法啓動,若是重複調用了,則將拋出以上的異常IllegalThreadStateException

建立線程代碼以下:

package com.shsxt.thread;

/** * 多線程的建立,方式一:繼承於Thread類 */
//1. 建立一個繼承於Thread類的子類
class MyThread extends Thread {
    //2. 重寫Thread類的run()
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        //3. 建立Thread類的子類的對象
        MyThread mt = new MyThread();
        //4.經過此對象調用start():①啓動當前線程 ② 調用當前線程的run()
        mt.start();
        //問題一:咱們不能經過直接調用run()的方式啓動線程。
        //mt.run();

        //問題二:再啓動一個線程,遍歷100之內的偶數。不能夠還讓已經start()的線程去執行。會報IllegalThreadStateException
        //t1.start();

        //咱們須要從新建立一個線程的對象
        MyThread mt2 = new MyThread();
        mt2.start();

        //以下操做仍然是在main線程中執行的。
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i + "******main*******");
            }
        }

    }
}

複製代碼
public class ThreadDemo {

    public static void main(String[] args) {

        //建立Thread類的匿名子類的方式
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100 ; i++) {
                    if (i%2==0){
                        System.out.println(Thread.currentThread().getName()+":"+i);
                    }
                }
            }
        }.start();

        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if(i % 2 != 0){
                        System.out.println(Thread.currentThread().getName() + ":" + i);

                    }
                }
            }
        }.start();
    }
}
複製代碼

四、Thread類有關方法

  • void start(): 啓動線程,並執行對象的run()方法

  • run(): 線程在被調度時執行的操做

  • String getName(): 返回線程的名稱

  • void setName(String name):設置該線程名稱

  • static Thread currentThread(): 返回當前線程。在Thread子類中就是this,一般用於主線程和Runnable實現類

  • static void yield():線程讓步

    暫停當前正在執行的線程,把執行機會讓給優先級相同或更高的線程

  • join():在線程a中調用線程b的join(),此時線程a就進入阻塞狀態,直到線程b徹底執行完之後,線程a才結束阻塞狀態。

  • static void sleep(long millis) :(指定時間:毫秒);讓當前線程「睡眠」指定的millitime毫秒。在指定的millitime毫秒時間內,當前線程是阻塞狀態。

  • boolean isAlive():返回boolean,判斷線程是否還活着

五、線程調度

  • 調度策略

    1. 時間片:
    2. 搶佔式: 高優先級的線程搶佔CPU
  • Java的調度方法

    1. 同優先級線程組成先進先出隊列(先到先服務),使用時間片策略
    2. 對高優先級,使用優先調度的搶佔式策略

六、線程的優先級

  • 線程的優先級等級:

    MAX_PRIORITY :10 MIN _PRIORITY :1 NORM_PRIORITY :5

  • 涉及的方法

    getPriority() :返回線程優先值 setPriority(int newPriority) :改變線程的優先級

  • 注意點:

    線程建立時繼承父線程的優先級

    低優先級只是得到調度的機率低,並不是必定是在高優先級線程以後才被調用

關於方法的一些使用,代碼以下:

package com.shsxt.thread;

class HelloThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                try {
                    sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(getName() + ":" + getPriority() + ":" + i);
            }

            //yield()方法的使用(禮讓線程)
// if (i % 20 == 0) {
// yield();
// }
        }
    }

    public HelloThread(String name) {
        //給子線程賦名字
        super(name);
    }
}

public class ThreadMethod {
    public static void main(String[] args) {
        //第一種方式:給子線程賦名字
        HelloThread h1 = new HelloThread("Thread:1");
        //第二種方式:給子線程賦名字
        //h1.setName("線程一");

        //給子線程設置優先級
        //h1.setPriority(Thread.MAX_PRIORITY);
        //啓動子線程
        h1.start();

        //給主線程命名
        Thread.currentThread().setName("主線程");
        //給主線程設置優先級
        //Thread.currentThread().setPriority(Thread.MIN_PRIORITY);

        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
            }
            //調用join()方法,當i==20時主線程阻塞,子線程運行完後,主線程才運行
            if (i == 20){
                try {
                    h1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        //判斷線程是否還存活着
        System.out.println(h1.isAlive());
    }
}
複製代碼

使用繼承Thread類,寫一個窗口賣票的練習

class Window extends Thread{
    //使用static關鍵字是防止new Window()每一個線程都有100張票
    //不使用static關鍵字的話,須要用到建立線程的第二種方式實現Runnable接口
    private static int tickets = 100;
    @Override
    public void run() {
        while (true){
            if (tickets>0){
                System.out.println(getName()+":賣票,票號爲"+tickets);
                tickets--;
            }else {
                break;
            }
        }
    }
}

public class WindowTest {
    public static void main(String[] args) {
        Window w = new Window();
        Window w1 = new Window();
        Window w2 = new Window();

        w.setName("窗口一");
        w1.setName("窗口二");
        w2.setName("窗口三");

        w.start();
        w1.start();
        w2.start();

    }
}
複製代碼

七、建立線程的第二種方式(實現Runnable接口)

  • 實現Runnable接口

    1. 建立一個實現了Runnable接口的類
    2. 實現類去實現Runnable中的抽象方法:run()
    3. 建立實現類的對象
    4. 將此對象做爲參數傳遞到Thread類的構造器中,建立Thread類的對象
    5. 經過Thread類的對象調用start()

線程建立,代碼以下:

package com.shsxt.thread;

//1. 建立一個實現了Runnable接口的類
class MyThread1 implements Runnable {
    //二、實現類去實現Runnable中的抽象方法:run()
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }

        }
    }
}

public class ThreadTest1 {

    public static void main(String[] args) {
        //3. 建立實現類的對象
        MyThread1 myThread1 = new MyThread1();

        //4. 將此對象做爲參數傳遞到Thread類的構造器中,建立Thread類的對象
        Thread t1 = new Thread(myThread1);
        t1.setName("線程1");
        //5. 經過Thread類的對象調用start():① 啓動線程 ②調用當前線程的run()-->調用了Runnable類型的target的run()
        t1.start();

        //再啓動一個線程,遍歷100之內的偶數
        Thread t2 = new Thread(myThread1);
        t2.setName("線程2");
        t2.start();

    }

}
複製代碼

八、建立線程(繼承Thread類和實現Runnable接口)的兩種方式的異同

相同點:

兩種方式都須要重寫run(),將線程要執行的邏輯聲明在run()中。

不一樣點:

開發中:優先選擇:實現Runnable接口的方式

緣由:一、實現了Runnable接口的方式解決了類的單繼承性的侷限性

二、實現Runnable接口的方式更適合來處理多個線程有共享數據的狀況。

使用繼承Thread類,寫一個窗口賣票的練習:

package com.shsxt.thread;

/** * 建立三個窗口賣票,總票數爲100張.使用實現Runnable接口的方式 * @author Rainbow * @date 2020/7/15 10:31 */
class Window1 implements Runnable {
    private int tickets = 100;

    @Override
    public void run() {
        while (true) {
            if (tickets > 0) {
                System.out.println(Thread.currentThread().getName() + ":賣票,票號爲:" + tickets);
                tickets--;
            } else {
                break;
            }
        }
    }
}

public class WindowTest1 {

    public static void main(String[] args) {
        Window1 w1 = new Window1();

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

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

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

複製代碼

3、線程的生命週期

  • JDK 中用Thread.State 類定義了 線程的幾種狀態

要想實現多線程,必須在主線程中建立新的線程對象。Java語言使用Thread類及其子類的對象來表示線程,在它的一個完整的生命週期中一般要經歷以下的 五種狀態:

  • 新建:當一個Thread類或其子類的對象被聲明並建立時,新生的線程對象處於新建狀態
  • 就緒:處於新建狀態的線程被start()後,將進入線程隊列等待CPU時間片,此時它已具有了運行的條件,只是沒分配到CPU資源
  • 運行:當就緒的線程被調度並得到CPU資源時,便進入運行狀態, run()方法定義了線程的操做和功能
  • 阻塞:在某種特殊狀況下,被人爲掛起或執行輸入輸出操做時,讓出 CPU 並臨時停止本身的執行,進入阻塞狀態
  • 死亡:線程完成了它的所有工做或線程被提早強制性地停止或出現異常致使結束

一、線程生命週期圖

4、線程的同步

首先舉個例子看下:

package com.shsxt.day;

/** * @author Rainbow * @date 2020/7/15 9:15 */

class Window extends Thread {

    private static int tickets = 100;

    @Override
    public void run() {
        while (true) {
            if (tickets > 0) {
                System.out.println(getName() + ":賣票,票號爲" + tickets);
                tickets--;
            } else {
                break;
            }
        }
    }
}

/** * @author Rainbow */
public class WindowTest {
    public static void main(String[] args) {
        Window w = new Window();
        Window w1 = new Window();
        Window w2 = new Window();

        w.setName("窗口一");
        w1.setName("窗口二");
        w2.setName("窗口三");

        w.start();
        w1.start();
        w2.start();

    }
}

複製代碼

由此代碼看出,發現有線程安全問題:理想狀態下

極端狀態:

  • 由上述代碼能夠看出出現了線程安全問題

  • 問題的緣由:

    當多條語句在操做同一個線程共享數據時,一個線程對多條語句只執行了一部分,尚未執行完,另外一個線程參與進來執行。致使共享數據的錯誤。

  • 解決辦法:

    對多條操做共享數據的語句,只能讓一個線程都執行完,在執行過程當中,其餘線程不能夠參與執行。即便有條線程發生了阻塞,也不能改變,也得等這條線程執行完畢,其餘線程才能執行

一、Synchronized的使用方法

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

    1. 同步代碼塊

      synchronized(同步監視器){

      ​ //須要被同步的代碼

      }

    2. synchronized 還能夠放在方法聲明中,表示整個方法爲同步方法

      public synchronized void show (){ …. }

  • 關於以上名詞的說明:

    同步的代碼:操做共享數據的代碼 ----------->(同步的範圍)同步的代碼不能被同步代碼塊包含多了,也不能包含少了

    ***同步監視器(俗稱:鎖)***:任何一個類的對象,均可以充當鎖。要求:多個線程必須共同擁有一把鎖

  • 同步機制中的鎖和注意事項:

    一、任意對象均可以做爲同步鎖。全部對象都自動含有單一的鎖(監視器)

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

    三、同步代碼塊:本身指定,不少時候也是指定爲this或類名.class

    注意事項:

    一、必須確保使用同一個資源的 多個線程共用一把鎖,這個很是重要,不然就沒法保證共享資源的安全

    二、 一個線程類中的全部靜態方法共用同一把鎖(類名.class),全部非靜態方法共用同一把鎖(this),同步代碼塊(指定需謹慎)

  • 使用同步方式的優缺點:

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

    缺點:操做同步代碼時,只能有一個線程參與,其餘線程等待。至關因而一個單線程的過程,效率低

二、使用同步代碼塊的方式解決實現Runnable接口的線程安全問題

  • 代碼以下:
package com.shsxt.thread;

/** * 在實現Runnable接口建立多線程的方式中,咱們能夠考慮使用this充當同步監視器。 * @author Rainbow * @date 2020/7/15 16:22 */
class Window1 implements Runnable {
    private int ticket = 100;
    //使用同步代碼塊的第一種解決方式
    Object obj = new Object();

    @Override
    public void run() {
        while (true) {
// synchronized (obj) {
           //使用同步代碼塊的第二種解決方式
            synchronized (this) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":賣票,票號爲:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
// }

        }
    }
}

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();
    }
}
複製代碼

三、使用同步代碼塊解決繼承Thread類的方式的線程安全問題

package com.shsxt.thread;

/** * 在繼承Thread類建立多線程的方式中,慎用this充當同步監視器,考慮使用當前類充當同步監視器。 * * @author Rainbow * @date 2020/7/15 16:33 */
class Window2 extends Thread {
    private static int ticket = 100;
    private static Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            //正確的方式:
// synchronized (obj) {
            synchronized (Window2.class) { //Class clazz = Window2.class,Window2.class只會加載一次
                //錯誤的方式:
// 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();

    }
}

複製代碼

四、使用同步方法解決實現Runnable接口的線程安全問題

package com.shsxt.thread;

/** * @author Rainbow * @date 2020/7/15 16:40 */
class Window3 implements Runnable {
    private int ticket = 100;
    boolean flag = true;

    @Override
    public void run() {

        while (flag) {
            show();
        }
    }

    private synchronized void show() {//同步監視器:this
        //至關於下面的方式
// synchronized (this) {
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":賣票,票號爲:" + ticket);
            ticket--;
        } else {
            flag = false;
        }
// }
    }
}

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();
    }
}

複製代碼

五、使用同步方法處理繼承Thread類的方式中的線程安全問題

package com.shsxt.thread;


/** * 使用同步方法處理繼承Thread類的方式中的線程安全問題 * @author Rainbow * @date 2020/7/15 16:59 */
class Window4 extends Thread {
    static boolean flag = true;
    private static int ticket = 100;


    @Override
    public void run() {

        while (flag) {
            show();
        }
    }

    private static synchronized void show() {//同步監視器:Window4.class
        //沒有static關鍵字修飾,此時的同步監視器爲:t1,t2,t3;此種解決方式是錯誤的
// private synchronized void show(){
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":賣票,票號爲:" + ticket);
            ticket--;
        } else {
            flag = false;
        }
// }

    }
}

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();

    }
}
複製代碼

六、單例設計模式之懶漢式( 線程安全)

package com.shsxt.thread;

/** * 單例線程安全的懶漢模式 * @author Rainbow * @date 2020/7/15 17:08 */
class Singleton {
    private static Singleton instance = null;

    private Singleton() {

    }

    public static Singleton getInstance() {

        //方式1、效率低
// synchronized (Singleton.class) {
// if (instance == null) {
// instance = new Singleton();
// }
// return instance;
// }

        //方式2、效率高
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

public class SingletonTest {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        Singleton instance3 = Singleton.getInstance();

        System.out.println(instance);
        System.out.println(instance1);
        System.out.println(instance2);
        System.out.println(instance3);

    }

}
複製代碼

5、線程的死鎖問題

  • 死鎖問題的產生

    1. 不一樣的線程分別佔用對方須要的同步資源不放棄,都在等待對方放棄本身須要的同步資源,就造成了線程的死鎖
    2. 出現死鎖後,不會出現異常,不會出現提示,只是全部的線程都處於阻塞狀態,沒法繼續
  • 解決方法

    用專門的算法、原則

    儘可能減小同步資源的定義

    儘可能避免嵌套同步

  • 案例

    package com.shnsxt.thread;
    
    /** * @author Rainbow * @date 2020/7/15 21:28 */
    public class ThreadTest {
        public static void main(String[] args) {
            StringBuffer s1 = new StringBuffer();
            StringBuffer s2 = new StringBuffer();
    
            new Thread(){
                @Override
                public void run() {
                    synchronized (s1){
                        s1.append("a");
                        s2.append("1");
    
                        //增大產生死鎖的概率
                        try {
                            sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                        synchronized (s2){
                            s1.append("b");
                            s2.append("2");
    
                            System.out.println(s1);
                            System.out.println(s2);
                        }
                    }
    
                }
            }.start();
    
    
            new Thread(){
                @Override
                public void run() {
                    synchronized (s2){
                        s1.append("c");
                        s2.append("3");
                    }
    
                    try {
                        sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    synchronized (s1){
                        s1.append("d");
                        s2.append("4");
    
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }.start();
    
        }
    }
    複製代碼

6、建立線程的第三種方式:Lock鎖(JDK5.0提供)

  • 從JDK 5.0開始,Java提供了更強大的線程同步機制——經過顯式定義同步鎖對象來實現同步。同步鎖使用Lock對象充當。
  • java.util.concurrent.locks.Lock接口是控制多個線程對共享資源進行訪問的工具。鎖提供了對共享資源的獨佔訪問,每次只能有一個線程對Lock對象加鎖,線程開始訪問共享資源以前應先得到Lock對象
  • ReentrantLock 類實現了 Lock ,它擁有與 synchronized 相同的併發性和內存語義,在實現線程安全的控制中,比較經常使用的是ReentrantLock,能夠顯式加鎖、釋放鎖。

一、Synchronized與Lock的異同?

  • 相同點:

    兩者均可以解決線程安全問題

  • 不一樣點:

    一、synchronized機制在執行完相應的同步代碼之後,自動的釋放同步監視器

    二、Lock須要手動的啓動同步(lock()方法)緊跟try代碼塊同時結束同步也須要手動的實現(unlock()方法)且必須放在finally的首行

  • 優先使用順序

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

使用Lock鎖的案例:

package com.shnsxt.thread;

import java.util.concurrent.locks.ReentrantLock;

/** * @author Rainbow * @date 2020/7/15 21:41 */
class Window implements Runnable {
    private int ticket = 100;
    //1.實例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                //2.調用鎖定方法lock()
                lock.lock();

                if (ticket > 0) {

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

                    System.out.println(Thread.currentThread().getName() + ":售票,票號爲:" + ticket);
                    ticket--;
                } else {
                    break;
                }

            } finally {
                //3.調用解鎖方法:unlock(),且必須放在finally的首行
                lock.unlock();

            }
        }
    }
}

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

        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();
    }
}
複製代碼

7、對以上知識點的一個小練習

package com.shnsxt.thread;

/** * 銀行有一個帳戶。 * 有兩個儲戶分別向同一個帳戶存3000元,每次存1000,存3次。每次存完打印帳戶餘額。 * @author Rainbow * @date 2020/7/15 21:52 */

class Account{
    private double balance;

    public Account(double balance) {
        this.balance = balance;
    }

    //存錢
    public synchronized void deposit(double AMB) {//同步監視器:this;雖說使用繼承Thread類慎用this,可是此處的this不表明Customer,而是Account
        if (AMB>0){
            balance+=AMB;

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

            System.out.println(Thread.currentThread().getName() + ":存錢成功。餘額爲:" + balance);
        }
    }
}
//儲戶
class Customer extends Thread{
    private Account account;

    public Customer(Account account) {
        this.account = account;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3 ; i++) {
            account.deposit(1000);
        }
    }
}
public class AccountTest {
    public static void main(String[] args) {
        Account account = new Account(0);
        Customer customer = new Customer(account);
        Customer customer1 = new Customer(account);

        customer.setName("甲");
        customer1.setName("已");

        customer.start();
        customer1.start();

    }
}

複製代碼
package com.shnsxt.thread;

/** * @author Rainbow * @date 2020/7/16 9:37 */

class Blank {
    private String accountId;
    private double balance;

    public Blank(String accountId, double balance) {
        this.accountId = accountId;
        this.balance = balance;
    }

    public double getBalance() {
        return balance;
    }

    public String getAccountId() {
        return accountId;
    }

    public void setAccountId(String accountId) {
        this.accountId = accountId;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    @Override
    public String toString() {
        return "Blank{" +
                "accountId='" + accountId + '\'' +
                ", balance=" + balance +
                '}';
    }
}

class DrawThread extends Thread {
    private Blank blank;
    //取款額度
    private double money;

    public DrawThread(String name, Blank blank, double money) {
        super(name);
        this.blank = blank;
        this.money = money;
    }

    @Override
    public void run() {
        synchronized (blank) {
            if (blank.getBalance() > money) {
                System.out.println(Thread.currentThread().getName() + ":取款成功," + "取現的金額爲" + money);

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

                blank.setBalance(blank.getBalance() - money);

            } else {
                System.out.println("取現額度超過帳戶餘額,取款失敗");
            }
        }
        System.out.println(blank.getAccountId() + "帳戶的餘額爲:" + blank.getBalance());
    }
}

public class DrawThreadTest {

    public static void main(String[] args) {
        Blank blank = new Blank("中國銀行", 100000.00);

        DrawThread d1 = new DrawThread("張三", blank, 4000);
        DrawThread d2 = new DrawThread("李四", blank, 5000);
        DrawThread d3 = new DrawThread("王五", blank, 8000);

        d1.start();
        d2.start();
        d3.start();
    }

}
複製代碼

8、線程的通訊

  • 首先先給個案例,先理解下:使用兩個線程印 打印 1-100 。線程1, 線程2 交替打印
package com.shnsxt.thread;

/** * @author Rainbow * @date 2020/7/15 22:07 */

class Number implements Runnable {
    private int number = 1;
    private Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (obj) {
                if (number <= 100) {

                    //喚醒被wait的一個線程。
                    obj.notify();

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

                    System.out.println(Thread.currentThread().getName() + ":" + number);
                    number++;

                    try {
                        //使得調用以下wait()方法的線程進入阻塞狀態
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    break;
                }
            }
        }
    }
}

public class CommunicationTest {
    public static void main(String[] args) {
        Number number = new Number();
        Thread t1 = new Thread(number);
        Thread t2 = new Thread(number);

        t1.setName("線程1");
        t2.setName("線程2");

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

複製代碼
  • 圖解:

一、關於上述代碼中涉及到三個方法:wait() 與 notify() 和 notifyAll()

  • wait():一旦執行此方法,當前線程就進入阻塞狀態,並釋放同步監視器(鎖)。
  • notify():一旦執行此方法,就會喚醒被wait的一個線程,若是有多個線程被wait,就喚醒優先級高的那個。
  • notifyAll():一旦執行此方法,就會喚醒全部被wait的線程。

二、關於這三個方法的注意點:

  • wait()notify()notifyAll()三個方法必須使用在同步代碼塊或同步方法中
  • wait()notify()notifyAll()三個方法的調用者必須是同步代碼塊或同步方法中的同步監視器(鎖)。不然,會出現IllegalMonitorStateException異常
  • wait()notify()notifyAll()三個方法是定義在java.lang.Object類中。

三、sleep()和wait()方法的異同(面試題)

  • 相同點:一旦執行方法,均可以使得當前的線程進入阻塞狀態。

  • 不一樣點:

    一、兩個方法聲明的位置不一樣:Thread類中聲明sleep() , Object類中聲明wait()

    二、調用的要求(範圍)不一樣:sleep()能夠在任何須要的場景下調用,wait()必須使用在同步代碼塊同步方法中

    三、關因而否釋放同步監視器(鎖):若是兩個方法都使用在同步代碼塊或同步方法中,sleep()不會釋放鎖wait()會釋放鎖

9、關於線程通訊問題總結小練習(消費者與生產者)

package com.shnsxt.thread;

/** * 經典例題:生產者/消費者的問題 * * @author Rainbow * @date 2020/7/16 9:07 */
class Clerk {
    private int productCount = 0;

    /** * 生產產品 */
    public synchronized void produceProduct() {
        if (productCount < 20) {
            productCount++;
            System.out.println(Thread.currentThread().getName() + ":開始生產第" + productCount + "產品");

            //線程喚醒
            this.notify();

        } else {

            //線程等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /** * 消費產品 */
    public synchronized void consumerProduct() {
        if (productCount > 0) {
            System.out.println(Thread.currentThread().getName() + ":開始消費第" + productCount + "產品");
            productCount--;

            //線程喚醒
            this.notify();

        } else {
            //線程等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

/** * 生產者 */
class Producer extends Thread {
    private Clerk clerk;

    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(getName() + ": 開始生產產品......");
        while (true) {

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

            //生產產品
            clerk.produceProduct();
        }

    }
}

/** * 消費者 */
class Consumer extends Thread {
    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(getName() + ": 開始消費產品......");

        while (true) {

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

            //消費產品
            clerk.consumerProduct();
        }
    }
}

public class ProducetTest {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        //生產者
        Producer p1 = new Producer(clerk);
        p1.setName("生產者");
        //消費者
        Consumer c1 = new Consumer(clerk);
        c1.setName("消費者1");
        Consumer c2 = new Consumer(clerk);
        c2.setName("消費者2");

        p1.start();
        c1.start();
        c2.start();
    }

}
複製代碼

10、建立線程的第四種方式之一:實現Callable接口(JDK5.0新增)

  • 與使用Runnable相比, Callable功能更強大些

    一、 相比run()方法,能夠有返回值

    二、 方法能夠拋出異常

    三、支持泛型的返回值

    四、須要藉助FutureTask類,好比獲取返回結果

  • Future接口

    一、 能夠對具體Runnable、Callable任務的執行結果進行取消、查詢是否完成、獲取結果等。

    二、FutrueTask是Futrue接口的惟一的實現類

    三、 FutureTask 同時實現了Runnable, Future接口。它既能夠做爲Runnable被線程執行,又能夠做爲Future獲得Callable的返回值

代碼理解,以下所示:

package com.shnsxt.thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/** * 建立線程的方式三:實現Callable接口。 --- JDK 5.0新增 * 1. call()能夠有返回值的。 * 2. call()能夠拋出異常,被外面的操做捕獲,獲取異常的信息 * 3. Callable是支持泛型的 * * @author Rainbow * @date 2020/7/16 10:31 */
//1.建立一個實現Callable的實現類
class NumThread implements Callable<Integer> {

    //2.實現call方法,將此線程須要執行的操做聲明在call()方法中
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            if (i % 2 == 0) {
                sum += i;
            }
        }
        return sum;
    }
}

public class ThreadCallable {
    public static void main(String[] args) {
        //3.建立Callable接口實現類的對象
        NumThread numThread = new NumThread();

        //4.將此Callable接口實現類的對象做爲傳遞到FutureTask構造器中,建立FutureTask的對象
        FutureTask<Integer> futureTask = new FutureTask(numThread);

        //5.將FutureTask的對象做爲參數傳遞到Thread類的構造器中,建立Thread對象,並調用start()
        new Thread(futureTask).start();

        try {
            //6.獲取Callable中call方法的返回值
            //get()返回值即爲FutureTask構造器參數Callable實現類重寫的call()的返回值。
            Integer sum = futureTask.get();
            System.out.println("總和爲:" + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}
複製代碼

11、建立線程的第四種方式之二:使用線程池(JDK5.0新增)

  • 爲何要用線程池?

    常常建立和銷燬、使用量特別大的資源,好比並髮狀況下的線程,對性能影響很大。

  • 使用線程池的好處

    一、提升響應速度(減小了建立新線程的時間) 二、下降資源消耗(重複利用線程池中線程,不須要每次都建立)

    便於線程的管理:

    一、corePoolSize:核心池的大小

    二、maximumPoolSize:最大線程數

    三、keepAliveTime:線程沒有任務時最多保持多長時間後會終止

  • 線程池相關的API

    JDK 5.0起提供了線程池相關API:ExecutorServiceExecutors

    ExecutorService:真正的線程池接口。常見子類ThreadPoolExecutor

    1. void execute(Runnable command) :執行任務/命令,沒有返回值,通常用來執行Runnable
    2. <T> Future <T> submit(Callable<T> task):執行任務,有返回值,通常又來執行Callable
    3. void shutdown() :關閉鏈接池

    Executors:工具類、線程池的工廠類,用於建立並返回不一樣類型的線程池

    1. Executors.newCachedThreadPool():建立一個可根據須要建立新線程的線程池,主要的問題是線程數最大數Integer.MAX_VALUE,可能會建立數量很是多的線程
    2. Executors.newFixedThreadPool(n): 建立一個可重用固定線程數的線程池,主要問題是堆積的請求處理隊列可能會損耗很是大的內存
    3. Executors.newSingleThreadExecutor() :建立一個只有一個線程的線程池,主要問題是堆積的請求處理隊列可能會損耗很是大的內存
    4. Executors.newScheduledThreadPool(n):建立一個線程池,它可安排在給定延遲後運行命令或者按期地執行。主要的問題是線程數最大數Integer.MAX_VALUE,可能會建立數量很是多的線程

代碼理解,以下所示:

package com.shnsxt.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;


/** * 建立線程的方式四之二:使用線程池 * 好處: * 1.提升響應速度(減小了建立新線程的時間) * 2.下降資源消耗(重複利用線程池中線程,不須要每次都建立) * 3.便於線程管理 * corePoolSize:核心池的大小 * maximumPoolSize:最大線程數 * keepAliveTime:線程沒有任務時最多保持多長時間後會終止 * * @author Rainbow * @date 2020/7/16 11:08 */
class NumberThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

class NumberThread1 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            if (i % 2 != 0) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

public class ThreadPool {
    public static void main(String[] args) {

        //1. 提供指定線程數量的線程池
        ExecutorService service = Executors.newFixedThreadPool(10);//這裏可能會出現上面寫出的問題
        ThreadPoolExecutor pool = (ThreadPoolExecutor) service;
        //設置線程池的屬性
// pool.setCorePoolSize(15);
// pool.setKeepAliveTime(60,MINUTES);

        //2.執行指定的線程的操做。須要提供實現Runnable接口或Callable接口實現類的對象
        service.execute(new NumberThread());//適合適用於Runnable
        service.execute(new NumberThread1());//適合適用於Runnable

// service.submit(Callable callable);//適合使用於Callable

        //3.關閉鏈接池
        service.shutdown();
    }
}
複製代碼

若是,此文章對你有所幫助,請幫忙點個讚唄! 謝謝!!!

勵志圖
相關文章
相關標籤/搜索