1. 程式人生 > 其它 >vue element 多級選單 巢狀路由跳轉問題

vue element 多級選單 巢狀路由跳轉問題

多執行緒技術(Java.Thread)

1、基本概念

1.1、程式

​ 程式(Program)是一個靜態概念,一般對應於作業系統中的一個可執行檔案。雙擊可執行檔案,將會載入該程式到記憶體中並開始執行它,於是就產生了“程序”。

1.2、程序

​ 執行中的程式叫做程序(Process),這是一個動態的概念。現代的作業系統都可以同時啟動多個程序。

程序的特點:

  • 程序是程式的一次動態執行過程,佔用特定的地址空間。
  • 每個程序由三部分組成:CPU、Data、Code。每個程序都是獨立的,保有自己的CPU時間、程式碼和資料。即便用同一份程式產生好幾個程序,它們之間還是擁有自己的這三樣東西,這造成的缺點是浪費記憶體,CPU的負擔較重。
  • 多工(Multitasking)作業系統將CPU時間動態地劃分給每個程序,作業系統同時執行多個程序,每個程序獨立執行。以程序的觀點來看,它會以為自己獨佔CPU的使用權。
  • 程序的檢視方法:
    • Windows系統:快捷鍵Ctrl+Alt+Del啟動工作管理員檢視所有程序。
    • Unix系統:使用ps或top命令。

1.3、執行緒

​ 一個程序可以產生多個執行緒(Thread)。執行緒與多個程序可以共享作業系統的某些資源一樣,同一程序的多個執行緒也可以共享此程序的某些資源(例如程式碼和資料),所以執行緒又被稱為輕量級程序(Lightweight Process)。

執行緒的特點:

  • 一個程序內部的一個執行單元,它是程式中的一個單一的順序控制流程。
  • 一個程序可擁有多個並行的(concurrent)執行緒
  • 一個程序中的多個執行緒共享相同的記憶體單元/記憶體地址空間,可以訪問相同的變數和物件,而且它們從同一個堆中分配物件並進行通訊、資料交換和同步操作。
  • 由於執行緒間的通訊是在同一地址空間上進行的,所以不需要額外的通訊機制,這就使得通訊更簡便而且資訊的傳遞速度也更快。
  • 執行緒的啟動、中斷和消亡所消耗的資源非常少。

1.4、執行緒和程序的區別

  • 每個程序都有獨立的程式碼和資料空間(程序上下文),程序間的切換會有較大的開銷。
  • 執行緒可以看成是輕量級的程序,屬於同一程序的執行緒共享程式碼和資料空間,每個執行緒都有獨立的執行棧和程式計數器(PC),執行緒切換的開銷小。
  • 執行緒和程序最根本的區別在於:程序是資源分配的單位,執行緒是排程和執行的單位
  • 多程序:在作業系統中能同時執行多個任務(程式)。
  • 多執行緒:在同一應用程式中有多個順序流同時執行。
  • 執行緒是程序的一部分,所以執行緒有時候被稱為輕量級程序。
  • 一個沒有執行緒的程序是可以被看作單執行緒的。如果一個程序內擁有多個執行緒,程序的執行過程不是一條線(執行緒),而是多條線(執行緒)共同完成的。
  • 系統在執行的時候會為每個程序分配不同的記憶體區域,但是不會為執行緒分配記憶體(執行緒所使用的資源是它所屬的程序的資源),執行緒組只能共享資源。就是說,除了CPU之外(執行緒在執行的時候要佔用CPU資源),計算機內部的軟硬體資源的分配與執行緒無關,執行緒只能共享它所屬程序的資源。

1.5、程序和程式的區別

​ 程式是一組指令的集合,它是靜態的實體,沒有執行的含義,而程序是一個動態的實體,有自己的生命週期。一般說來,一個程序肯定與一個程式相對應,並且只有一個,但是一個程式可以有多個程序,或者一個程序都沒有。除此之外,程序還有併發性和交往性。簡單地說,程序是程式的一部分,程式執行的時候會產生程序

2、Java中的多執行緒

2.1、通過繼承Thread類實現多執行緒

​ 繼承Thread類實現多執行緒的步驟:

  1. 在Java中負責實現執行緒功能的類是java.lang.Thread類。
  2. 可以通過建立Thread的例項來建立新的執行緒。
  3. 每個執行緒都是通過某個特定的Thread物件所對應的方法run()來完成其操作的,方法run()稱為執行緒體。
  4. 通過呼叫Thread類的start()方法來啟動一個執行緒。

通過繼承Thread類來實現多執行緒:

public class ThreadDemo01 extends Thread{  //自定義繼承Thread類
    //run()方法裡是執行緒體
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(this.getName() + ":" + i); //getName()方法用於返回執行緒名稱
        }
    }

    public static void main(String[] args) {
        ThreadDemo01 thread1 = new ThreadDemo01();   //建立執行緒物件
        thread1.start();                             //啟動執行緒
        ThreadDemo01 thread2 = new ThreadDemo01();   //建立執行緒物件
        thread2.start();
    }
}

如果不是.start()而是去呼叫run()方法.run()只有主執行緒一條路徑,會先執行run,執行完再繼續執行主執行緒,就不是多執行緒了。

這個出現結果是因為電腦太快了,次數多了就會穿插。實際上兩條執行緒是一起跑的。

這種方式的缺點是:如果類已經繼承了一個類(例如小程式必須繼承自Applet類),則無法再繼承Thread類。

網圖下載器:

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

//練習Thread,實現多執行緒同步下載圖片
public class TestThreadDemo03 extends Thread{

    private String url;  //網路圖片地址
    private String name; //儲存的檔名

    public TestThreadDemo03(String url,String name) {
        this.url = url;
        this.name = name;
    }

    //下載圖片執行緒的執行體
    @Override
    public void run() {
        WebDownLoader webDownLoader = new WebDownLoader();
        webDownLoader.downLoader(url,name);
        System.out.println("下載了檔名為:" + name);
    }

    public static void main(String[] args) {
        TestThreadDemo03 t1 = new TestThreadDemo03("http://h2.ioliu.cn/bing/Neowise_ZH-CN1308687945_640x480.jpg","彗星.jpg");
        TestThreadDemo03 t2 = new TestThreadDemo03("http://h2.ioliu.cn/bing/CapelCurig_ZH-CN5115677414_640x480.jpg","國家公園.jpg");
        TestThreadDemo03 t3 = new TestThreadDemo03("http://h2.ioliu.cn/bing/SaguaroFamily_ZH-CN3845395676_640x480.jpg","仙人掌.jpg");

        t1.start();
        t2.start();
        t3.start();
        //三個執行緒是同時執行的
    }
}

//下載器
class WebDownLoader {
    //下載方法
    public void downLoader(String url, String name) {
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO異常,downloader方法出現問題");
        }
    }
}

2.2、通過Runnable介面實現多執行緒

​ 在開發中,更多的是通過Runnable介面實現多執行緒。這種方式克服了通過繼承Thread類實現執行緒類的缺點,即在實現Runnable介面的同時還可以繼承某個類。所以實現Runnable介面的方式要通用一些。

通過Runnable介面實現多執行緒:

public class RunnableDemo01 implements Runnable{  //自定義類實現Runnable介面
    //run()方法裡是執行緒體
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }

    public static void main(String[] args) {
        //建立執行緒物件,把實現了Runnable介面的物件作為引數傳入
        Thread thread1 = new Thread(new RunnableDemo01());
        thread1.start(); //啟動執行緒
        Thread thread2 = new Thread(new RunnableDemo01());
        thread2.start();
    }
}

2.3、通過實現Callable介面實現多執行緒

  1. 實現Callable介面,需要返回值型別
  2. 重寫call方法,需要丟擲異常
  3. 建立目標物件
  4. 建立執行服務: ExecutorService ser = Executors.newFixedThreadPool(1);
  5. 提交執行:Future< Boolean > result1 = ser.submit(t1);
  6. 獲取結果:boolean r1 = result1.get()
  7. 關閉服務:ser.shutdownNow();
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

//執行緒建立方式三:實現Callable介面
public class TestCallable implements Callable<Boolean> {
    private String url;  //網路圖片地址
    private String name; //儲存的檔名

    public TestCallable(String url,String name) {
        this.url = url;
        this.name = name;
    }

    //下載圖片執行緒的執行體
    @Override
    public Boolean call() {
        WebDownLoader webDownLoader = new WebDownLoader();
        webDownLoader.downLoader(url,name);
        System.out.println("下載了檔名為:" + name);
        return true;
    }

    public static void main(String[] args) throws Exception{
        TestCallable t1 = new TestCallable("http://h2.ioliu.cn/bing/Neowise_ZH-CN1308687945_640x480.jpg","彗星.jpg");
        TestCallable t2 = new TestCallable("http://h2.ioliu.cn/bing/CapelCurig_ZH-CN5115677414_640x480.jpg","國家公園.jpg");
        TestCallable t3 = new TestCallable("http://h2.ioliu.cn/bing/SaguaroFamily_ZH-CN3845395676_640x480.jpg","仙人掌.jpg");

        //建立執行服務
        ExecutorService ser = Executors.newFixedThreadPool(3);

        //提交執行
        Future<Boolean> r1 = ser.submit(t1);
        Future<Boolean> r2 = ser.submit(t2);
        Future<Boolean> r3 = ser.submit(t3);

        //獲取結果
        boolean rs1 = r1.get();
        boolean rs2 = r2.get();
        boolean rs3 = r3.get();
        
        //列印返回值
        System.out.println(rs1);//true
        System.out.println(rs2);//true
        System.out.println(rs3);//true

        //關閉服務
        ser.shutdownNow();

    }
}

//下載器
class WebDownLoader {
    //下載方法
    public void downLoader(String url, String name) {
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO異常,downloader方法出現問題");
        }
    }
}

實現Callable介面的好處:

  1. 可以定義返回值
  2. 可以丟擲異常

2.4、初識併發

//多個執行緒同時操作同一個物件
//買火車票的例子


//發現問題:多個執行緒操作同一個資源的情況下,執行緒不安全,資料紊亂
public class TestThreadDemo04 implements Runnable{

    //票的數量
    private int ticketNums = 10;

    @Override
    public void run() {
        while (true) {

            if (ticketNums <= 0) {
                break;
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNums-- + "張票");
        }
    }

    public static void main(String[] args) {
        TestThreadDemo04 ticket = new TestThreadDemo04();

        new Thread(ticket,"小明").start();
        new Thread(ticket,"小紅").start();
        new Thread(ticket,"黃牛").start();
    }
}

發生了好幾個人搶到了一張票的情況。

2.5、靜態代理模式

public class StaticProxy {
    public static void main(String[] args) {
        WeddingCompany weddingCompany = new WeddingCompany(new You());
        weddingCompany.HappyMarry();
    }
}

interface Marry{
    void HappyMarry();
}

//真實角色
class You implements Marry {
    @Override
    public void HappyMarry() {
        System.out.println("小明結婚了");
    }
}

//代理角色,幫助真實角色
class WeddingCompany implements Marry{

    //代理誰-->真實目標角色
    private Marry target;

    public WeddingCompany(Marry target) {
        this.target = target;
    }

    @Override
    public void HappyMarry() {
        before();
        this.target.HappyMarry(); //這就是真實物件
        after();
    }

    private void after() {
        System.out.println("結婚之後桀驁不馴");
    }

    private void before() {
        System.out.println("結婚之前唯唯諾諾");
    }
}

代理模式可以在不修改被代理物件的基礎上,通過擴充套件代理類,進行一些功能的附加與增強。值得注意的是,代理類和被代理類應該共同實現一個介面,或者是共同繼承某個類。

上面介紹的是靜態代理的內容,為什麼叫做靜態呢?因為它的型別是事先預定好的。比如WeddingCompany這個類。

這裡的婚慶公司就相當於一個Thread

new Thread(()->System.out.println("我愛你")).start;
new weddingcompany(new You()).HappyMarry;

3、Lambda表示式

  • λ 希臘字母,英語名稱為Lambda
  • 避免匿名內部類定義過多

3.1、函式式介面

  • 任何介面,如果只包含唯一一個抽象方法,那麼它就是一個函式式介面。

    public interface Runnable {
        public abstract void run();
    }
    
  • 對於函式式介面,我們可以通過lambda表示式來建立該介面的物件。

TestLambda1.java:

/*
推導lambda表示式
 */
public class TestLambda1 {

    //3、靜態內部類
    static class Like2 implements ILike{
        @Override
        public void lambda() {
            System.out.println("I like lambda2");
        }
    }

    public static void main(String[] args) {

        ILike like = new Like();
        like.lambda();

        like = new Like2();
        like.lambda();

        //4、區域性內部類
        class Like3 implements ILike{
            @Override
            public void lambda() {
                System.out.println("I like lambda3");
            }
        }

        like = new Like3();
        like.lambda();

        //5、匿名內部類,沒有類的名稱,必須藉助介面或者父類
        like = new ILike() {
            @Override
            public void lambda() {
                System.out.println("I like lambda4");
            }
        };
        like.lambda();

        //6、用lambda簡化
        like = ()-> {
            System.out.println("I like lambda5");
        };
        like.lambda();

    }
}

//1、定義一個函式式介面
interface ILike{
    void lambda();
}
//2、實現類
class Like implements ILike{
    @Override
    public void lambda() {
        System.out.println("I like lambda");
    }
}

TestLambda2.java:

public class TestLambda2 {
    public static void main(String[] args) {
        ILove love = null;

        love = (int a)->{
            System.out.println("I love you-->" + a);
        };

        //簡化1:去掉引數型別
        love = (a)->{
            System.out.println("I love you-->" + a);
        };

        //簡化2:簡化括號
        love = a->{
            System.out.println("I love you-->" + a);
        };

        //簡化3:去掉大括號
        love = a -> System.out.println("I love you-->" + a);

        //總結:
        //lambda表示式只能在有一行程式碼的情況下,才能簡化大括號,如果有多行,就用程式碼塊包裹。
        //前提是介面為函式式介面
        //多個引數也可以去掉引數型別,要去掉就都去掉

        love.m(521);
    }
}

interface ILove{
    void m(int a);
}

總結:
1.lambda表示式只能在有一行程式碼的情況下,才能簡化大括號,如果有多行,就用程式碼塊包裹。
2.前提是介面為函式式介面
3.多個引數也可以去掉引數型別,要去掉就都去掉

4、執行緒狀態和生命週期

4.1、執行緒狀態

​ 一個執行緒物件在它的生命週期內,需要經歷5個狀態:

1.新生狀態(New)

​ 用new關鍵字建立一個執行緒物件後,該執行緒物件就處於新生狀態。處於新生狀態的執行緒有自己的記憶體空間,通過呼叫start方法進入就緒狀態。

2.就緒狀態(Runnable)

​ 處於就緒狀態的執行緒已經具備了執行條件,但是還沒有被分配到CPU,處於“執行緒就緒佇列”,等待系統為其分配CPU。就緒狀態並不是執行狀態,當系統選定一個等待執行的Thread物件後,它就會進入執行狀態。一旦獲得CPU,執行緒就進入執行狀態並自動呼叫其的run()方法。下列四種原因會導致執行緒進入就緒狀態。

  1. 新建執行緒:呼叫start()方法,進入就緒狀態。
  2. 阻塞執行緒:阻塞解除,進入就緒狀態。
  3. 執行執行緒:呼叫yield()方法,直接進入就緒狀態。
  4. 執行執行緒:JVM將CPU資源從本執行緒切換到其他執行緒。

3.執行狀態(Running)

​ 在執行狀態的執行緒執行其run方法中的程式碼,直到因呼叫其他方法而終止,或等待某資源產生阻塞或完成任務死亡。如果給定的時間片內沒有執行結束,執行緒就會被系統換下來並回到就緒狀態,也可能由於某些“導致阻塞的事件”而進入阻塞狀態。

4.阻塞狀態(Blocked)

​ 阻塞是指暫停一個執行緒的執行以等待某個條件發生(如某資源準備就緒)。有四種原因會導致阻塞:

  • 執行sleep(int millsecond)方法,使當前執行緒休眠,進入阻塞狀態。當指定的時間到了之後,執行緒進入就緒狀態。
  • 執行wait()方法,使當前執行緒進入阻塞狀態。當使用notfity()方法喚醒這個執行緒後,它進入就緒狀態。
  • 當執行緒執行時,某個操作進入阻塞狀態,例如執行I/O流操作(read()/write()方法本身就是阻塞的方法)。只有當引起該操作的阻塞的原因消失後,執行緒才進入就緒狀態。
  • join()執行緒聯合:當某個執行緒等待另一個執行緒執行結束並能繼續執行時,使用join()方法。

5.死亡狀態(Terminated)

​ 死亡狀態是執行緒生命週期中的最後一個階段。執行緒死亡的原因有兩個:一個是正常執行的執行緒完成了它run()方法內的全部工作;另一個是執行緒被強制終止,如通過執行stop()或destory()方法來終止一個執行緒(注:stop()和destory()方法已被廢棄)。

​ 當一個執行緒進入死亡狀態之後,就不能再回到其他狀態了。

4.2、終止執行緒的典型方式

​ 終止執行緒通常的做法是提供一個boolean型的終止變數,當這個變數置為false時,終止執行緒的執行。

終止執行緒的典型方法(重要):

public class ThreadCiycle implements Runnable{
    String name;
    boolean live = true; //標記變數,表示執行緒是否可中止

    public ThreadCiycle(String name) {
        super();
        this.name = name;
    }
    public void run() {
        int i = 0;
        //當live的值是true時,繼續執行緒體;是false時則結束迴圈,繼而終止執行緒體
        while (live) {
            System.out.println(name + (i++));
        }
    }
    public void terminate() {
        live = false;
    }

    public static void main(String[] args) {
        ThreadCiycle ttc = new ThreadCiycle("執行緒A:");
        Thread t1 = new Thread(ttc);   //新生狀態
        t1.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("主執行緒" + i);
        }
        ttc.terminate();
        System.out.println("ttc stop!");
    }
}

執行這個專案,同時跑兩個執行緒,執行緒A和主執行緒,所以每次執行結果不一定相同。

4.3、暫停執行緒執行的常用方法

​ 暫停執行緒執行的常用方法有sleep()和yield(),這兩個方法的區別如下:

  • sleep()方法可以讓正在執行的執行緒進入阻塞狀態,直到休眠時間滿了,進入就緒狀態。單位是毫秒。每個物件都有一把鎖,sleep不會釋放鎖。
  • yield()方法可以讓正在執行的執行緒直接進入就緒狀態,讓出CPU的使用權。

暫停執行緒的方法——sleep():

public class SleepDemo {
    public static void main(String[] args) {
        StateThread thread1 = new StateThread();
        thread1.start();
        StateThread thread2 = new StateThread();
        thread2.start();
    }
}
//使用繼承方式實現多執行緒
class StateThread extends Thread {
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(this.getName() + ":" + i);
            try {
                Thread.sleep(2000); //呼叫執行緒的sleep()方法
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

這只是部分結果,每兩行為一對,每一對輸出後有延遲才會輸出下一對,這是Thread.sleep(2000); 語句在起作用。

模擬倒計時和列印當前系統時間:

import java.text.SimpleDateFormat;
import java.util.Date;

//模擬倒計時
public class TestSleep2 {

    public static void main(String[] args) {
//        try {
//            tenDown();
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }//倒計時

        //列印當前系統時間
        Date startTime = new Date(System.currentTimeMillis()); //獲取系統當前時間

        while (true) {
            try {
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime)); //這個hh小寫,時間為12小時制,HH大寫,時間為24小時制,永遠慢1秒
                startTime = new Date(System.currentTimeMillis()); //更新當前時間
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //模擬倒計時
    public static void tenDown() throws InterruptedException {
        int num = 10;

        while (true) {
            Thread.sleep(1000);
            System.out.println(num--);
            if (num<0) {
                break;
            }
        }
    }
}

暫停執行緒的方法——yield:

禮讓執行緒,讓執行緒從執行狀態轉為就緒狀態,禮讓不一定成功。

public class YieldDemo {
    public static void main(String[] args) {
        StateThread thread1 = new StateThread();
        thread1.start();
        StateThread thread2 = new StateThread();
        thread2.start();
    }
}

//使用繼承方式實現多執行緒
class StateThread1 extends Thread {
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(this.getName() + ":" + i);
            Thread.yield(); //呼叫執行緒yield方法
        }
    }
}

部分結果,程式碼執行引起執行緒的切換,但執行沒有明顯延遲。

4.4、聯合執行緒的方法

​ 執行緒A在執行期間,可以呼叫執行緒B的join()方法,讓執行緒B和執行緒A聯合。這樣,執行緒A就必須等待執行緒B執行完畢,才能繼續執行。

執行緒的聯合-join():

public class TestThreadState {
    public static void main(String[] args) {
        System.out.println("爸爸和兒子買菸的故事");
        Thread father = new Thread(new FatherThread());
        father.start();
    }
}

class FatherThread implements Runnable {
    public void run() {
        System.out.println("爸爸想抽菸,發現煙抽完了");
        System.out.println("爸爸想讓兒子去買包中華");
        Thread son = new Thread(new SonThread());
        son.start();
        System.out.println("爸爸等兒子買菸回來");
        try {
            son.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("爸爸出門去找兒子跑哪去了");
            //結束JVM。如果是0則表示正常結束;如果是非0則表示非正常結束
            System.exit(1);
        }
        System.out.println("爸爸高高興興地接過煙開始抽,並把零錢給了兒子");
    }
}

class SonThread implements Runnable {
    public void run() {
        System.out.println("兒子出門去買菸");
        System.out.println("兒子買菸需要10分鐘");
        try {
            for (int i = 0; i <= 10; i++) {
                System.out.println("第" + i + "分鐘");
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("兒子買菸回來了");
    }
}

”爸爸執行緒”要抽菸,於是就聯合了“兒子執行緒去買菸”,必須等待“兒子執行緒”買菸完畢,“爸爸執行緒”才能繼續抽菸。

5、執行緒的基本資訊和優先級別

5.1、觀測執行緒狀態

觀測執行緒的狀態:

//觀察測試執行緒的狀態
public class TestState {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("/////");
        });


        //觀察狀態
        Thread.State state = thread.getState();
        System.out.println(state); //New

        //觀察啟動後
        thread.start(); //啟動執行緒
        state = thread.getState();
        System.out.println(state); //Run

        while (state != Thread.State.TERMINATED) { //只要執行緒不終止,就一直輸出狀態
            Thread.sleep(100);
            state = thread.getState(); //更新執行緒狀態
            System.out.println(state); //輸出狀態
        }
        //死亡之後的執行緒不可重新啟動
    }
}

5.2、獲取執行緒基本資訊的方法

執行緒的常用方法
方法 功能
isAlive() 判斷執行緒是否還”活著“,即執行緒是否還未終止
getPriority() 獲得執行緒的優先順序數值。
setPriority() 設定執行緒的優先順序數值
setName() 給執行緒一個名字
getName() 取得執行緒的名字
currentThread() 取得當前正在執行的執行緒物件,也就是取得自己本身

執行緒的常用方法一:

public class TestThreadDemo01 {
    public static void main(String[] args) throws Exception {
        Runnable r = new MyThread();
        Thread t = new Thread(r,"Name test");   //定義執行緒物件,並傳入引數
        t.start();                                    //啟動執行緒
        System.out.println("name is:" + t.getName()); //輸出執行緒名稱
        Thread.sleep(5000);                    //執行緒暫停5秒鐘
        System.out.println(t.isAlive());             //判斷執行緒還在執行嗎
        System.out.println("over!");
    }
}

class MyThread implements Runnable{
    //執行緒體
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
        }
    }
}

這是兩個執行緒r,t同時在跑,是並行的。

5.3、執行緒的優先順序

​ 處於就緒狀態的執行緒,會進入“就緒佇列”等待JVM來挑選。

​ 執行緒的優先順序用數字表示,範圍從1~10,一個執行緒的預設優先順序是5。

​ 使用下列方法可獲得或設定執行緒物件的優先順序:

  • int getPriority()
  • void setPriority(int newPriority)

測試執行緒的優先順序:

//測試執行緒的優先順序
public class TestPriority {
    public static void main(String[] args) {
        //主執行緒預設優先順序 1-10 預設是5
        System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority() );

        MyPriority myPriority = new MyPriority();

        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);
        Thread t5 = new Thread(myPriority);
        Thread t6 = new Thread(myPriority);

        //先設定優先順序,再啟動
        t1.start();

        t2.setPriority(1);
        t2.start();

        t3.setPriority(4);
        t3.start();

        t4.setPriority(Thread.MAX_PRIORITY); //MAX_PRIORITY=10,最大優先順序
        t4.start();

//        t5.setPriority(-1); //丟擲異常
//        t5.start();
//
//        t6.setPriority(11); //丟擲異常
//        t6.start();
    }
}

class MyPriority implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
    }
}

注意:優先順序低只是意味著獲得排程的概率低,並不是絕對先呼叫優先順序高的執行緒後呼叫優先順序低的執行緒。

執行緒的常用方法二:

public class TestThreadDemo02 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyThread1(),"t1");
        Thread t2 = new Thread(new MyThread1(),"t2");
        t1.setPriority(1);
        t2.setPriority(10);
        t1.start();
        t2.start();
    }
}

class MyThread1 extends Thread {
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

5.4、守護(daemon)執行緒

  • 執行緒分為使用者執行緒守護執行緒

  • 虛擬機器必須確保使用者執行緒執行完畢(main()...)

  • 虛擬機器不用等待守護執行緒執行完畢(gc垃圾回收執行緒...)

    如,後臺記錄操作日誌,監控記憶體,垃圾回收等待

測試守護執行緒:

//測試守護執行緒
//上帝守護你
public class TestDaemon {
    public static void main(String[] args) {
        God god = new God();
        You you = new You();

        Thread thread = new Thread(god);
        thread.setDaemon(true);  //預設是false表示是使用者執行緒,正常的執行緒都是使用者執行緒。

        thread.start(); //上帝守護執行緒啟動

        Thread thread1 = new Thread(you);
        thread1.start(); //使用者執行緒啟動

    }
}

//上帝

class God implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("上帝保佑你!");
        }
    }
}

//你
class You implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("一生都開心的活著");
        }
        System.out.println("=======goodbye! world!");
    }
}

6、執行緒同步

​ 在處理多執行緒問題時,如果多個執行緒同時訪問同一個物件(併發),並且某些執行緒還想修改這個物件時,就需要用到“執行緒同步”機制。

6.1、什麼是執行緒同步

​ 執行緒同步其實就是一種等待機制,多個需要同時訪問此物件的執行緒進入這個物件的等待池形成佇列,等待前面的執行緒使用完畢後,下一個執行緒再使用。

執行緒同步的安全性:佇列 + 鎖

​ 由於同一程序的多個執行緒共享同一塊儲存空間,在帶來方便的同時也帶來了訪問衝突問題,為了保證資料在方法中被訪問時的正確性,在訪問時加入鎖機制 synchronized,當一個執行緒獲得物件的排它鎖,獨佔資源,其他執行緒必須等待,使用後釋放鎖即可。存在以下問題:

  • 一個執行緒持有鎖會導致其他所有需要此鎖的執行緒掛起;
  • 在多執行緒競爭下,加鎖,釋放鎖會導致比較多的上下文切換和排程延時,引起效能問題;
  • 如果一個優先順序高的執行緒等待一個優先順序低的執行緒釋放鎖會導致優先順序倒置,引起效能問題。

多執行緒操作同一個物件(未使用執行緒同步):

public class TestSyncDemo01 {
    public static void main(String[] args) {
        Account a1 = new Account(100,"高");//定義一個賬戶,叫高,裡面有100元
        Drawing draw1 = new Drawing(80,a1); //定義取錢執行緒物件
        Drawing draw2 = new Drawing(80,a1); //定義取錢執行緒物件
        draw1.start();                                   //你取錢
        draw2.start();                                   //你老婆取錢
    }
}

/*
簡單表示銀行賬戶
 */
class Account {
    int money;
    String aname;

    public Account(int money, String aname) {
        this.money = money;
        this.aname = aname;
    }
}

/*
模擬提款操作
 */
class Drawing extends Thread{
    int drawingNum;                                      //取多少錢
    Account account;                                     //要取錢的賬戶
    int expenseTotal;                                    //總共取的錢數

    public Drawing(int drawingNum, Account account) {
        this.drawingNum = drawingNum;
        this.account = account;
    }

    @Override
    public void run() {
        if (account.money - drawingNum < 0) {
            return;
        }
        try {
            Thread.sleep(1000);                          //判斷完後阻塞。其他執行緒開始執行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        account.money -= drawingNum; //account.money = account.money - drawingNum; 賬戶餘額
        expenseTotal += drawingNum; //expenseTotal = expenseTotal + drawingNum;    總共取的錢
        System.out.println(this.getName() + "--賬戶餘額:" + account.money); 
        System.out.println(this.getName() + "--總共取了" + expenseTotal);
    }
}

​ 這個例子由於沒有執行緒同步機制,導致兩個執行緒同時操作同一個賬戶物件,竟然從只有100元的賬戶中取出160元,使得賬戶餘額變成了-60元。

6.2、不安全案例

買票:

//不安全的買票
//執行緒不安全
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();

        new Thread(station,"小明").start();
        new Thread(station,"小紅").start();
        new Thread(station,"黃牛").start();
    }
}

class BuyTicket implements Runnable {

    private int ticketnums = 10;//票
    boolean flag = true; //外部停止方式


    @Override
    public void run() {
        //買票
        while (flag) {
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void buy() throws InterruptedException {
        //判斷是否有票
        if (ticketnums <=0 ) {
            flag = false;
            return;
        }

        Thread.sleep(1000);//延時

        System.out.println(Thread.currentThread().getName() + "拿到" + ticketnums--);//買票
    }
}

會出現幾個人拿到同一張票,會出現負數。

不安全的銀行:

//不安全的取錢
//兩個人去銀行取錢
public class UnsafeBank {
    public static void main(String[] args) {
        //賬戶
        Account account = new Account(100,"基金");

        Drawing you = new Drawing(account,50,"你");
        Drawing wife = new Drawing(account,100,"wife");

        you.start();
        wife.start();
    }
}

//賬戶
class Account {
    int money;   //餘額
    String name; //卡名

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}

//銀行:模擬取款
class Drawing extends Thread {
    Account account; //賬戶
    int drawingMoney; //取了多少錢
    int nowMoney; //現在手裡有多少錢


    public Drawing(Account account, int drawingMoney, String name) {
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    //取錢
    @Override
    public void run() {
        //判斷有沒有錢
        if (account.money - drawingMoney<0) {
            System.out.println(Thread.currentThread().getName() + "錢不夠,取不了");
            return;
        }

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

        //卡內餘額 = 餘額 - 取的錢
        account.money = account.money - drawingMoney;
        //手裡的錢
        nowMoney = nowMoney + drawingMoney;

        System.out.println(account.name + "餘額為:" + account.money);

        //this.getName() = Thread.currentThread().getName()
        System.out.println(this.getName() + "手裡的錢:" + nowMoney);
    }
}

6.3實現執行緒同步

​ 由於統一程序的多個執行緒共享同一塊儲存空間,這在帶來方便的同時,也帶來了訪問衝突問題。Java語言提供了專門機制來解決這種衝突,有效避免了同一個資料物件被多個執行緒同時訪問造成的問題。

​ synchronized方法控制對“物件”的訪問,每個物件對應一把鎖,每個synchronized方法都必須獲得呼叫該方法的物件的鎖才能執行,否則執行緒會阻塞,方法一旦執行,就獨佔該鎖,直到該方法返回才釋放鎖,後面被阻塞的執行緒才能獲得這個鎖,繼續執行。

缺陷:若將一個大的方法宣告為synchronized將會影響效率。

​ 由於人們可以通過private關鍵字來保證資料物件只能被方法訪問,所以只需針對方法提出一套機制即可。這套機制就是使用synchronized關鍵字,它包括兩種用法:synchronized方法和synchronized塊。

1.synchronized方法

​ 通過在方法宣告中加入synchronized關鍵字來宣告此方法,語法格式如下:

public synchronized void accessVal(int newVal);

​ synchronized方法控制對“物件的類成員變數”的訪問:每個物件對應一把鎖,每個synchronized方法都必須獲得呼叫該方法的鎖方能執行,否則所屬執行緒阻塞,方法一旦執行,就獨佔該鎖,直到從該方法返回時才將鎖釋放,此後被阻塞的執行緒方能獲得該鎖,重新進入可執行狀態。

買票的例子中,只需要把buy()方法用synchronized修飾即可

2.synchronized塊

​ synchronized的缺陷是,若將一個大的方法宣告為synchronized將會大大影響程式的工作效率。

​ 為此,Java提供了更好的解決辦法,就是使用synchronized塊。synchronized塊可以讓人們精確地控制具體的“成員變數”,縮小同步的範圍,提高效率。

​ 通過synchronized關鍵字可宣告synchronized塊,語法格式如下:

synchronized(synObject)
    
//允許訪問控制的程式碼
    

多執行緒操作同一個物件(使用執行緒同步):

public class TestSyncDemo02 {
    public static void main(String[] args) {
        Account a1 = new Account(100,"高");//定義一個賬戶,叫高,裡面有100元
        Drawing draw1 = new Drawing(80,a1); //定義取錢執行緒物件
        Drawing draw2 = new Drawing(80,a1); //定義取錢執行緒物件
        draw1.start();                                   //你取錢
        draw2.start();                                   //你老婆取錢
    }
}

/*
簡單表示銀行賬戶
 */
class Account {
    int money;
    String aname;

    public Account(int money, String aname) {
        this.money = money;
        this.aname = aname;
    }
}

/*
模擬提款操作
 */
class Drawing extends Thread {
    int drawingNum;                                      //取多少錢
    Account account;                                     //要取錢的賬戶
    int expenseTotal;                                    //總共取的錢數

    public Drawing(int drawingNum, Account account) {
        super();
        this.drawingNum = drawingNum;
        this.account = account;
    }

    @Override
    public void run() {
        draw();
    }

    void draw() {
        synchronized (account) {
            if (account.money - drawingNum < 0) {
                System.out.println(this.getName() + "取款,餘額不足!");
                return;
            }
            try {
                Thread.sleep(1000); //判斷完後阻塞。其他執行緒開始執行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.money -= drawingNum;
            expenseTotal += drawingNum;
        }
        System.out.println(this.getName() + "--賬戶餘額:" + account.money);
        System.out.println(this.getName() + "--總共取了" + expenseTotal);
    }
}

​ synchronized(account)意味著執行緒需要獲得account物件的“鎖”才有資格運行同步塊中的程式碼。Account物件的“鎖”也稱為“互斥鎖”,在同一時刻只能被一個執行緒使用。A執行緒擁有鎖,則可以呼叫“同步塊“中的程式碼;B執行緒沒有鎖,則進入account物件的”鎖池佇列“等待,直到A執行緒使用完畢釋放了account物件的鎖,B執行緒得到鎖才可以呼叫”同步塊“中的程式碼。

同理,應用到不安全的銀行的例子把run方法改成

/*
執行緒“you”執行run方法時發現加鎖了,便找到加鎖物件(you),發現沒有其他執行緒執行run方法,就持鎖執行run方法
執行緒“wife”執行run方法時發現加鎖了,便找到加鎖物件(wife,不是you,不是同一把鎖),也發現沒有其他執行緒執行run方法,就持鎖執行run方法
導致實際上兩個執行緒還是同步執行,所以應該鎖Account而不是Bank,兩個物件都是Account的例項化,synchroized是物件鎖,鎖應該去鎖共享物件。
 */
@Override
public  void run() {

    synchronized (account) {
        //判斷有沒有錢
        if (account.money - drawingMoney<0) {
            System.out.println(Thread.currentThread().getName() + "錢不夠,取不了");
            return;
        }

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

        //卡內餘額 = 餘額 - 取的錢
        account.money = account.money - drawingMoney;
        //手裡的錢
        nowMoney = nowMoney + drawingMoney;

        System.out.println(account.name + "餘額為:" + account.money);

        //this.getName() = Thread.currentThread().getName()
        System.out.println(this.getName() + "手裡的錢:" + nowMoney);
    }

鎖要鎖的是共享資源。

6.4、死鎖及解決方案

死鎖的概念

​ ”死鎖“指的是多個執行緒各自佔有一些共享資源,並且互相等待得到其他執行緒佔有的資源後才能繼續,從而導致兩個或者多個執行緒都在等待對方釋放資源,停止執行的情形。

​ 因此,某一個同步塊(synchronized)需要同時擁有”兩個以上物件的鎖“時,就可能會發生”死鎖“的問題。

死鎖問題演示:

class Lipstick{} //口紅類

class Mirror{}  //鏡子類

class Makeup extends Thread { //化妝繼承了Thread類
    int flag;
    String girl;
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    @Override
    public void run() {
        doMakeup();
    }

    void doMakeup() {
        if (flag == 0) {
            synchronized (lipstick) { //需要得到口紅的“鎖”
                System.out.println(girl + "拿著口紅!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (mirror) { //需要得到鏡子的“鎖”
                    System.out.println(girl + "拿著鏡子!");
                }
            }
        } else {
            synchronized (mirror) {
                System.out.println(girl + "拿著鏡子!");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lipstick) {
                    System.out.println(girl + "拿著口紅!");
                }
            }
        }
    }
}

public class TestDeadLock01 {
    public static void main(String[] args) {
        Makeup m1 = new Makeup(); //大丫的化妝執行緒
        m1.girl = "大丫";
        m1.flag = 0;
        Makeup m2 = new Makeup(); //小丫的化妝執行緒
        m2.girl = "小丫";
        m1.flag = 1;
        m1.start();
        m2.start();

    }
}

這個案例中,”化妝執行緒“需要同時擁有”鏡子物件“和”口紅物件“才能運行同步塊。在實際執行時,”小丫的化妝執行緒“擁有了”鏡子物件“,”大丫的化妝執行緒“擁有了”口紅物件“,都在互相等待對方釋放資源後才能化妝。這樣,兩個執行緒就形成了互相等待,無法繼續執行的”死鎖狀態“。

再舉個例子,小王和小李搶A票和B票,小王先買A後買B,小李先買B後買A;小王買完A等著買B,小李買完B等著買A,但是想要的票被對方買走了,就乾等著。

死鎖的解決方法

​ 死鎖是由於”同步塊需要同時持有多個物件鎖“造成的,要解決這個問題,思路很簡單,就是同一個程式碼塊不要同時持有兩個物件鎖。

死鎖問題的解決:

class Lipstick {} //口紅類

class Mirror {} //鏡子類

class Makeup extends Thread {
    int flag;
    String girl;
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    @Override
    public void run() {
        doMakeup();
    }

    void doMakeup() {
        if (flag == 0) {
            synchronized (lipstick) {
                System.out.println(girl + "拿著口紅!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            synchronized (mirror) {
                System.out.println(girl + "拿著鏡子!");
            }
        } else {
            synchronized (mirror) {
                System.out.println(girl + "拿著鏡子!");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            synchronized (lipstick) {
                System.out.println(girl + "拿著口紅!");
            }
        }
    }
}

public class TestDeadLock02 {
    public static void main(String[] args) {
        Makeup m1 = new Makeup();
        m1.girl = "大丫";
        m1.flag = 0;
        Makeup m2 = new Makeup();
        m2.girl = "小丫";
        m2.flag = 1;
        m1.start();
        m2.start();
    }

}

6.5、Lock鎖

  • 從JDK5.0開始,Java提供了更強大的執行緒同步機制——通過顯式定義同步鎖物件來實現同步。同步鎖使用Lock物件充當
  • java.util.concurrent.locks.Lock介面是控制多個執行緒對共享資源進行訪問的工具。鎖提供了對共享資源的獨佔訪問,每次只能有一個執行緒對Lock物件加鎖,執行緒開始訪問共享資源之前應先獲得Lock物件
  • ReentrantLock類(可重入鎖)實現了Lock,它擁有與synchronized相同的併發性和記憶體語義,在實現執行緒安全的控制中,比較常用的是ReentrantLock,可以顯式加鎖、釋放鎖。
import java.util.concurrent.locks.ReentrantLock;

public class TestLock {
    public static void main(String[] args) {
        TestLock2 testLock2 = new TestLock2();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
        new Thread(testLock2).start();

    }
}

class TestLock2 implements Runnable {

    int ticketNums = 10;

    //定義lock鎖
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock(); //加鎖
                if (ticketNums>0) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketNums--);
                }else {
                    break;
                }
            }finally {
                lock.unlock();//解鎖
            }

        }
    }
}

synchronized與Lock的對比

  • Lock是顯示鎖(手動開啟和關閉鎖,別忘記關閉鎖),synchronized是隱式鎖,出了作用域自動釋放
  • Lock只有程式碼塊鎖,synchronized有程式碼塊鎖和方法鎖
  • 使用Lock鎖,JVM將花費較少的時間來排程執行緒,效能更好。並且具有更好的擴充套件性(提供更多的子類)
  • 優先使用順序:
    • Lock > 同步程式碼塊(已經進入了方法體,分配了相應資源) > 同步方法(在方法體之外)

7、執行緒併發協作(生產者-消費者模式)

​ 多執行緒環境下,經常需要多個執行緒能夠併發和協作。這時,就需要了解一個重要的多執行緒併發協作模型”生產者-消費者模式“(並不是23種設計模式之一)。

  • 生產者指的是負責生產資料的模組(這裡的模組指的可能是方法、物件、執行緒和程序等)。
  • 消費者指的是負責處理資料的模組(這裡的模組指的可能是方法、物件、執行緒和程序等)。
  • 消費者不能直接使用生產者的資料,它們之間有個”緩衝區“。生產者將生產好的資料放入”緩衝區“,消費者從”緩衝區“拿出要處理的資料。

​ 緩衝區是實現併發操作的核心。緩衝區的設定有3個好處:

  • 實現執行緒的併發協作。有了緩衝區以後,生產者執行緒只需要往緩衝區裡面放置資料,而不需要管消費者消費的情況;同樣,消費者只需要從緩衝區拿出資料處理即可,不需要考慮生產者生產的情況。這樣,就從邏輯上實現了“生產者執行緒”和“消費者執行緒”的分離。
  • 解耦了生產者和消費者。生產者不需要和消費者直接打交道。
  • 解決忙閒不均,提高效率。生產者生產資料慢時,但在緩衝區仍有資料,不影響消費者消費;消費者處理資料慢時,生產者仍然可以繼續往緩衝區裡面放置資料。

生產者與消費者模式:

public class TestProduce {
    public static void main(String[] args) {
        SyncStack sStack = new SyncStack();                   //定義緩衝區連結
        Shengchan sc = new Shengchan(sStack);                 //定義生產執行緒
        Xiaofei xf = new Xiaofei(sStack);                     //消費執行緒
        sc.start();
        xf.start();
    }
}

class Mantou {                                              //饅頭
    int id;

    Mantou(int id) {
        this.id = id;
    }
}
class SyncStack {                                           //緩衝區(相當於:饅頭筐)
    int index = 0; //容器計數器
    Mantou[] ms = new Mantou[10];
    public synchronized void push(Mantou m) {
        while (index == ms.length) {                       //說明饅頭筐滿了
            try {
                //wait後,執行緒會將持有的鎖釋放,進入阻塞狀態
                //這樣其他需要鎖的執行緒就可以獲得鎖
                this.wait();
                //這裡的含義是執行此方法的執行緒暫停,進入阻塞狀態
                //等消費者消費了饅頭後再生產
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //喚醒在當前物件等待池中等待的第一個執行緒
        //notifyAll叫醒所有在當前物件等待池中等待的所有執行緒
        this.notify();
        //如果不喚醒的話,以後這兩個執行緒都會進入等待執行緒,沒有人喚醒
        ms[index] = m;
        index ++;
    }
    public synchronized Mantou pop() {
        while (index == 0) {                                      //如果饅頭筐是空的
            try {
                //如果饅頭筐是空的,就暫停消費此執行緒(因為沒什麼可消費的)
                this.wait();                                    //等生產執行緒生產完再來消費
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.notify();
        index--;
        return ms[index];
    }
}
class Shengchan extends Thread {                     //生產者執行緒
    SyncStack ss = null;
    public Shengchan(SyncStack ss) {
        this.ss = ss;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("生產饅頭:" + i);
            Mantou m = new Mantou(i);
            ss.push(m);
        }
    }
}
class Xiaofei extends Thread{                        //消費者執行緒
    SyncStack ss = null;

    public Xiaofei(SyncStack ss) {
        this.ss = ss;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            Mantou m = ss.pop();
            System.out.println("消費饅頭:" + i);
        }
    }
}

​ 執行緒併發協作(也叫執行緒通訊)通常用於生產者-消費者模式,情景如下:

  1. 生產者和消費者共享同一個資源,並且生產者和消費者之間相互依賴,互為條件。
  2. 對於生產者,沒有生產產品之前,消費者要進入等待狀態。而生產了產品之後,又需要馬上通知消費者消費。
  3. 對於消費者,在消費之後,要通知生產者已經消費結束,需要繼續生產新產品以供消費。
  4. 在生產者-消費者問題中,僅使用synchronzied是不夠的,synchronized可阻止併發更新同一個共享資源,雖然實現了同步,但它不能用來實現不同執行緒之間的訊息傳遞(通訊)。
執行緒通訊常用方法
方法名 作用
final void wait() 表示執行緒一直等待,直到得到其他執行緒通知,與sleep不同,會釋放鎖。
void wait(long timeout) 執行緒等待指定毫秒引數的時間
final void wait(long timeout,int nanos) 執行緒等待指定毫秒、微秒的時間
final void notify() 喚醒一個處於等待狀態的執行緒
final void notifyAll() 喚醒同一個物件上所有呼叫wait()方法的執行緒,優先級別高的執行緒優先執行

以上方法均是java.lang.Object類的方法,只能在同步方法或者同步程式碼塊中使用,否則會丟擲異常。

8、執行緒池

  • 背景:經常建立和銷燬、使用量特別大的資源,比如併發情況下的執行緒,對效能影響很大。

  • 思路:提前建立好多個執行緒,放入執行緒池中,使用時直接獲取,使用完放回池中。可以避免頻繁建立銷燬、實現重複利用。類似生活中的公共交通工具。

  • 好處:

    • 提高響應速度(減少了建立新執行緒的時間)
    • 降低資源消耗(重複利用執行緒池中執行緒,不需要每次都建立)
    • 便於執行緒管理(...)
      • corePoolSize:核心池的大小
      • maximumPoolSize:最大執行緒數
      • keepAliveTime:執行緒沒有任務時最多保持多長時間後會終止
  • JDK5.0起提供了執行緒池相關API:ExecutorService和Executors

  • ExecutorService:真正的執行緒池介面。常見子類ThreadPoolExecutor

    • void execute(Runnable command):執行任務/命令,沒有返回值,一般用來執行Runnable
    • < T >Future< T >submit(Callable< T > task):執行任務,有返回值,一般用來執行Callable
    • void shutdown():關閉連線池
  • Executors:工具類、執行緒池的工廠類,用於建立並返回不同型別的執行緒池

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

//測試執行緒池
public class TestPool {

    public static void main(String[] args) {
        //1.建立服務,建立執行緒池
        //newFixedThreadPool 引數為:執行緒池大小
        ExecutorService service = Executors.newFixedThreadPool(10);

        //執行
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        //2.關閉連線
        service.shutdown();
    }
}

class MyThread implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}