多執行緒,執行緒類三種方式,執行緒排程,執行緒同步,死鎖,執行緒間的通訊,阻塞佇列,wait和sleep區別?

趕星而至發表於2021-01-03

                      重難點梳理

                      知識點梳理

 

 

 

                          學習目標

1、能夠知道什麼是程式什麼是執行緒(程式和執行緒的概述,多程式和多執行緒的意義)

2、能夠掌握執行緒常見API的使用

3、能夠理解什麼是執行緒安全問題

4、能夠知道什麼是鎖

5、能夠知道什麼是死鎖

6、能夠掌握執行緒3種建立方式(3種建立方式)

7、能夠知道什麼是等待喚醒機制

 

                          超詳細講義

==知識點==

  1. 多執行緒的概念

  2. 多執行緒的實現方式

  3. 執行緒類的常見方法

  4. 執行緒同步

  5. 死鎖

  6. 生產者消費者

1.多執行緒的概念

1.1初步瞭解多執行緒【重點】

1.什麼是多執行緒?

採用多執行緒技術可以同時執行多個任務(比如:迅雷同時下載多個任務),多執行緒需要硬體支援

1.2併發和並行【重點】

1.什麼是並行?

在同一時刻,有多條執行緒在多個CPU上同時執行

2.什麼是併發?

在一段時間內,有多條執行緒在單個CPU上交替執行

1.3程式和執行緒【重點】

(共4點)

1.什麼是程式?

可以理解為正在執行的程式

2.什麼是執行緒?

執行緒它是程式的一部分,是程式中的單個控制流,是一條執行路徑(執行一項任務)

一個程式,可以有多條執行緒,至少有一條執行緒(執行緒才是CPU執行的最小單元)

執行緒只能在程式中執行

==3.為什麼要學習多執行緒==

可以讓程式同時執行多個任務,提高程式的執行效率(例如一次上傳多張圖片,同時開啟多個網頁)

2.多執行緒的實現方式

2.1實現多執行緒方式一:繼承Thread類【重點】

1.實現多執行緒的方式有哪些?

  • 繼承Thread類的方式進行實現

  • 實現Runnable介面的方式進行實現

  • 利用Callable和Future介面方式實現

2.方法介紹

方法名說明
void run() 線上程開啟後,此方法將被呼叫執行
void start() 使此執行緒開始執行,Java虛擬機器會呼叫run方法()

3.實現步驟

  • 定義一個類MyThread繼承Thread類

  • 在MyThread類中重寫run()方法

  • 建立MyThread類的物件

  • 啟動執行緒

程式碼演示

package com.itheima.threaddemo1;

public class MyThread extends Thread{
   @Override
   public void run() {
       //程式碼就是執行緒在開啟之後執行的程式碼
       for (int i = 0; i < 100; i++) {
           System.out.println("執行緒開啟了" + i);
      }
  }
}

package com.itheima.threaddemo1;

public class Demo {
   public static void main(String[] args) {
       //建立一個執行緒物件
       MyThread t1 = new MyThread();
       //建立一個執行緒物件
       MyThread t2 = new MyThread();

       //t1.run();//表示的僅僅是建立物件,用物件去呼叫方法,並沒有開啟執行緒.
       //t2.run();
       //開啟一條執行緒
       t1.start();
       //開啟第二條執行緒
       t2.start();
  }
}

2.2 多執行緒的實現方式-兩個小問題【瞭解】

1.為什麼要重寫run()方法?

因為run()是用來封裝被執行緒執行的程式碼

2.run()方法和start()方法的區別?

run():封裝執行緒執行的程式碼,直接呼叫,相當於普通方法的呼叫

start():啟動執行緒;然後由JVM呼叫此執行緒的run()方法

2.3實現多執行緒方式二:實現Runnable介面【重點】

(共3點)

1.Thread構造方法

方法名說明
Thread(Runnable target) 分配一個新的Thread物件

2.實現步驟

  • 定義一個類MyRunnable實現Runnable介面

  • 在MyRunnable類中重寫run()方法

  • 建立MyRunnable類的物件

  • 建立Thread類的物件,把MyRunnable物件作為構造方法的引數

  • 啟動執行緒

程式碼演示

package com.itheima.threaddemo2;

public class MyRunnable implements Runnable{
   @Override
   public void run() {
       //執行緒啟動後執行的程式碼
       for (int i = 0; i < 100; i++) {
           System.out.println(Thread.currentThread().getName() + "第二種方式實現多執行緒" + i);
      }
  }
}

package com.itheima.threaddemo2;

public class Demo {
   public static void main(String[] args) {
       //建立了一個引數的物件
       MyRunnable mr = new MyRunnable();
       //建立了一個執行緒物件,並把引數傳遞給這個執行緒.
       //線上程啟動之後,執行的就是引數裡面的run方法
       Thread t1 = new Thread(mr);
       //開啟執行緒
       t1.start();


       MyRunnable mr2 = new MyRunnable();
       Thread t2 = new Thread(mr2);
       t2.start();

  }
}

==3.執行緒的執行是順序是隨機的==

2.4 實現多執行緒方式三: 實現Callable介面【重點】

1.方法介紹

方法名說明
V call() 計算結果,如果無法計算結果,則丟擲一個異常
FutureTask(Callable<V> callable) 建立一個 FutureTask,一旦執行就執行給定的 Callable
V get() 如有必要,等待計算完成,然後獲取其結果

2.實現步驟

  • 定義一個類MyCallable實現Callable介面

  • 在MyCallable類中重寫call()方法

  • 建立MyCallable類的物件

  • 建立Future的實現類FutureTask物件,把MyCallable物件作為構造方法的引數

  • 建立Thread類的物件,把FutureTask物件作為構造方法的引數

  • 啟動執行緒

  • 再呼叫get方法,就可以獲取執行緒結束之後的結果。

3.注意事項

get()方法的呼叫一定要在Thread類物件呼叫start()方法之後

程式碼演示

package com.itheima.threaddemo3;

import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {
   @Override
   public String call() throws Exception {
       for (int i = 0; i < 100; i++) {
           System.out.println("跟女孩表白" + i);
      }
       //返回值就表示執行緒執行完畢之後的結果
       return "答應";
  }
}


package com.itheima.threaddemo3;

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

public class Demo {
   public static void main(String[] args) throws ExecutionException, InterruptedException {
       //執行緒開啟之後需要執行裡面的call方法
       MyCallable mc = new MyCallable();

       //Thread t1 = new Thread(mc);

       //可以獲取執行緒執行完畢之後的結果.也可以作為引數傳遞給Thread物件
       FutureTask<String> ft = new FutureTask<>(mc);

       //建立執行緒物件
       Thread t1 = new Thread(ft);

       String s = ft.get();
       //開啟執行緒
       t1.start();

       //String s = ft.get();
       System.out.println(s);
  }
}

2.5 三種實現方式的對比【重點】

1.實現Runnable、Callable介面

  • 好處: 擴充套件性強,實現該介面的同時還可以繼承其他的類

  • 缺點: 程式設計相對複雜,不能直接使用Thread類中的方法

2.繼承Thread類

  • 好處: 程式設計比較簡單,可以直接使用Thread類中的方法

  • 缺點: 可以擴充套件性較差,不能再繼承其他的類

3.執行緒類中的常見方法

3.1設定和獲取執行緒名稱【重點】

方法介紹

方法名說明
void setName(String name) 將此執行緒的名稱更改為等於引數name
String getName() 返回此執行緒的名稱

程式碼演示

package com.itheima.threaddemo4;

public class MyThread extends Thread {

   public MyThread() {
}

   public MyThread(String name) {
       super(name);
  }

   @Override
   public void run() {
       for (int i = 0; i < 100; i++) {
           System.out.println(getName() + "@@@" + i);
      }
  }
}

package com.itheima.threaddemo4;

public class Demo {
   //1,執行緒是有預設名字的,格式:Thread-編號
   public static void main(String[] args) {
       MyThread t1 = new MyThread("小蔡");
       MyThread t2 = new MyThread("小強");

       //t1.setName("小蔡");
       //t2.setName("小強");

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

3.2 Tread方法-獲得當前執行緒物件【重點】

方法名說明
static Thread currentThread() 返回對當前正在執行的執行緒物件的引用
package com.itheima.threaddemo5;
public class Demo {
   public static void main(String[] args) {
       String name = Thread.currentThread().getName();
       System.out.println(name);
  }
}

3.3 執行緒休眠【應用】【重點】

1.相關方法

方法名說明
static void sleep(long millis) 使當前正在執行的執行緒停留(暫停執行)指定的毫秒數

程式碼演示

package com.itheima.threaddemo6;

public class MyRunnable implements Runnable {
   @Override
   public void run() {
       for (int i = 0; i < 100; i++) {
           try {
               Thread.sleep(100);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }

           System.out.println(Thread.currentThread().getName() + "---" + i);
      }
  }
}
package com.itheima.threaddemo6;

public class Demo {
   public static void main(String[] args) throws InterruptedException {
       /*System.out.println("睡覺前");
       Thread.sleep(3000);
       System.out.println("睡醒了");*/

       MyRunnable mr = new MyRunnable();

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

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

3.4 執行緒優先順序【瞭解】

1.優先順序相關方法

方法名說明
final int getPriority() 返回此執行緒的優先順序
final void setPriority(int newPriority) 更改此執行緒的優先順序執行緒預設優先順序是5;執行緒優先順序的範圍是:1-10

程式碼演示

package com.itheima.threaddemo7;

import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {
   @Override
   public String call() throws Exception {
       for (int i = 0; i < 100; i++) {
           System.out.println(Thread.currentThread().getName() + "---" + i);
      }
       return "執行緒執行完畢了";
  }
}
package com.itheima.threaddemo7;

import java.util.concurrent.FutureTask;

public class Demo {
   public static void main(String[] args) {
       //優先順序: 1 - 10 預設值:5
       MyCallable mc = new MyCallable();

       FutureTask<String> ft = new FutureTask<>(mc);

       Thread t1 = new Thread(ft);
       t1.setName("飛機");
       t1.setPriority(10);
       //System.out.println(t1.getPriority());//5
       t1.start();

       MyCallable mc2 = new MyCallable();

       FutureTask<String> ft2 = new FutureTask<>(mc2);

       Thread t2 = new Thread(ft2);
       t2.setName("坦克");
       t2.setPriority(1);
       //System.out.println(t2.getPriority());//5
       t2.start();
  }
}

 

3.5 守護執行緒【瞭解】

1.什麼是守護執行緒?

守護執行緒是程式執行的時候在後臺提供一種通用服務的執行緒(保鏢與主人)

2.守護執行緒的特點?

被守護的執行緒結束,不會立即結束,掙扎一會兒才結束

3.如何使用守護執行緒?

相關方法

方法名說明
void setDaemon(boolean on) 將此執行緒標記為守護執行緒,當執行的執行緒都是守護執行緒時,Java虛擬機器將退出

程式碼演示

package com.itheima.threaddemo8.example;


public class MyThread1 extends Thread{

   @Override
   public void run() {
       for (int i = 0; i < 100; i++) {
           System.out.println(this.getName()+"---"+i);
      }
  }
}
package com.itheima.threaddemo8.example;


public class MyThread1 extends Thread{

   @Override
   public void run() {
       for (int i = 0; i < 100; i++) {
           System.out.println(this.getName()+"---"+i);
      }
  }
}
package com.itheima.threaddemo8.example;


public class Test {

   public static void main(String[] args) {
       MyThread1 t1 = new MyThread1();//保鏢
       t1.setDaemon(true);
       MyThread2 t2 = new MyThread2();//主人
      t1.setName("保鏢");
      t2.setName("主人");
      t2.start();
      t1.start();
  }
}

4.執行緒同步

4.1賣票【難點】

  • 案例需求

    某電影院目前正在上映國產大片,共有100張票,而它有3個視窗賣票,請設計一個程式模擬該電影院賣票

  • 實現步驟

    • 定義一個類SellTicket實現Runnable介面,裡面定義一個成員變數:private int tickets = 100;

    • 在SellTicket類中重寫run()方法實現賣票,程式碼步驟如下

    • 判斷票數大於0,就賣票,並告知是哪個視窗賣的

    • 賣了票之後,總票數要減1

    • 票賣沒了,執行緒停止

    • 定義一個測試類SellTicketDemo,裡面有main方法,程式碼步驟如下

    • 建立SellTicket類的物件

    • 建立三個Thread類的物件,把SellTicket物件作為構造方法的引數,並給出對應的視窗名稱

    • 啟動執行緒

  • 程式碼實現

    package com.itheima.threaddemo9;

    public class Ticket implements Runnable {
       //票的數量
       private int ticket = 100;

       @Override
       public void run() {
           while(true){
               
                   if(ticket <= 0){
                       //賣完了
                       break;
                  }else{
                       try {
                           Thread.sleep(100);
                      } catch (InterruptedException e) {
                           e.printStackTrace();
                      }
                       ticket--;
                       System.out.println(Thread.currentThread().getName() + "在賣票,還剩下" + ticket + "張票");
                  }
              }
           
    }
    }

    package com.itheima.threaddemo9;

    public class Demo {
       public static void main(String[] args) {
           Ticket ticket = new Ticket();

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

           t1.setName("視窗一");
           t2.setName("視窗二");
           t3.setName("視窗三");

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

4.2 執行緒安全問題-原因分析【難點】

1.賣票出現了問題

  • 相同的票出現了多次

     

     

  • 出現了負數的票

     

     

     

    出現問題的根本原因?

    執行緒執行的隨機,在賣票的過程中CPU隨機的執行了多條執行緒(也可以說是在賣票的過程中多條執行緒操作了共享資料ticket)

4.3同步程式碼塊解決資料安全問題【重點】

1.如何解決上述問題呢?

  • 任意時刻只有一條執行緒可以操作共享變數

  • Java中如何解決?

同步程式碼塊格式:

synchronized(任意物件) { 
多條語句操作共享資料的程式碼
}

synchronized(任意物件):就相當於給程式碼加鎖了,任意物件就可以看成是一把鎖

2.synchronized同步程式碼塊的特點?

預設情況下是開啟的,只要有一個執行緒進去執行程式碼了,鎖就會關閉

當執行緒執行完出來時,鎖才會自動開啟

 

 

 

3.什麼樣的情況下,會有執行緒安全問題

3.1 多執行緒環境

3.2 有共享資料

3.3 有對共享資料的操作(增、刪、改(除了long、double類的基本資料型別直接賦值)、查)

程式碼演示

package com.itheima.threaddemo9;
public class Ticket implements Runnable {
   //票的數量
   private int ticket = 100;
   private Object obj = new Object();

   @Override
   public void run() {
       while(true){
           synchronized (obj){//多個執行緒必須使用同一把鎖.
               if(ticket <= 0){
                   //賣完了
                   break;
              }else{
                   try {
                       Thread.sleep(100);
                  } catch (InterruptedException e) {
                       e.printStackTrace();
                  }
                   ticket--;
                   System.out.println(Thread.currentThread().getName() + "在賣票,還剩下" + ticket + "張票");
              }
          }
      }
  }
}

package com.itheima.threaddemo9;

public class Demo {
   public static void main(String[] args) {
       /*Ticket ticket1 = new Ticket();
       Ticket ticket2 = new Ticket();
       Ticket ticket3 = new Ticket();

       Thread t1 = new Thread(ticket1);
       Thread t2 = new Thread(ticket2);
       Thread t3 = new Thread(ticket3);*/

       Ticket ticket = new Ticket();

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

       t1.setName("視窗一");
       t2.setName("視窗二");
       t3.setName("視窗三");

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

4.4 執行緒安全問題-鎖物件唯一【重點】

1.鎖物件為什麼要唯一?

不同執行緒如果鎖的不是同一個物件,就解決不了執行緒的安全問題

package com.itheima.threaddemo10;

public class MyThread extends Thread {
   private static int ticketCount = 100;
   private static Object obj = new Object();

   @Override
   public void run() {
       while(true){
           synchronized (obj){ //就是當前的執行緒物件.
               if(ticketCount <= 0){
                   //賣完了
                   break;
              }else{
                   try {
                       Thread.sleep(100);
                  } catch (InterruptedException e) {
                       e.printStackTrace();
                  }
                   ticketCount--;
                   System.out.println(Thread.currentThread().getName() + "在賣票,還剩下" + ticketCount + "張票");
              }
          }
      }
  }
}

package com.itheima.threaddemo010;

public class Demo {
   public static void main(String[] args) {
       MyThread t1 = new MyThread();
       MyThread t2 = new MyThread();

       t1.setName("視窗一");
       t2.setName("視窗二");

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

4.5同步方法解決資料安全問題【重點】

(共4點)

1.同步方法的格式

同步方法:就是把synchronized關鍵字加到方法上

修飾符 synchronized 返回值型別 方法名(方法引數) { 
	方法體;
}

2.同步方法的鎖物件是什麼呢?

this

public class MyRunnableCommon implements Runnable {
    private static int ticketCount = 100;
    @Override
    public void run() {
        while(true){
            if("視窗一".equals(Thread.currentThread().getName())){
                //同步方法
                boolean result = synchronizedMthod();
                if(result){
                    break;
                }
            }

            if("視窗二".equals(Thread.currentThread().getName())){
                //同步程式碼塊
                synchronized (this){
                    if(ticketCount == 0){
                       break;
                    }else{
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        ticketCount--;
                        System.out.println(Thread.currentThread().getName() + "在賣票,還剩下" + ticketCount + "張票");
                    }
                }
            }

        }
    }

    private  synchronized boolean synchronizedMthod() {
        if(ticketCount == 0){
            return true;
        }else{
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticketCount--;
            System.out.println(Thread.currentThread().getName() + "在賣票,還剩下" + ticketCount + "張票");
            return false;
        }
    }
}

3.靜態同步方法的格式

同步靜態方法:就是把synchronized關鍵字加到靜態方法上

修飾符 static synchronized 返回值型別 方法名(方法引數) { 
	方法體;
}

4.同步靜態方法的鎖物件是什麼呢?

類名.class

程式碼演示

package com.itheima.threaddemo011;

public class MyRunnable implements Runnable {
    private static int ticketCount = 100;

    @Override
    public void run() {
        while(true){
            if("視窗一".equals(Thread.currentThread().getName())){
                //同步方法
                boolean result = synchronizedMthod();
                if(result){
                    break;
                }
            }

            if("視窗二".equals(Thread.currentThread().getName())){
                //同步程式碼塊
                synchronized (MyRunnable.class){
                    if(ticketCount == 0){
                       break;
                    }else{
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        ticketCount--;
                        System.out.println(Thread.currentThread().getName() + "在賣票,還剩下" + ticketCount + "張票");
                    }
                }
            }

        }
    }

    private static synchronized boolean synchronizedMthod() {
        if(ticketCount == 0){
            return true;
        }else{
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticketCount--;
            System.out.println(Thread.currentThread().getName() + "在賣票,還剩下" + ticketCount + "張票");
            return false;
        }
    }
}


package com.itheima.threaddemo011;

public class Demo {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();

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

        t1.setName("視窗一");
        t2.setName("視窗二");

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

4.6 Lock鎖【重點】(視訊19 5‘’)

1.如何手動開關鎖呢?

使用lock

2.如何使用Lock?

Lock是介面不能直接例項化,這裡採用它的實現類ReentrantLock來例項化

ReentrantLock構造方法

方法名說明
ReentrantLock() 建立一個ReentrantLock的例項

加鎖解鎖方法

方法名說明
void lock() 獲得鎖
void unlock() 釋放鎖

程式碼演示

package com.itheima.threaddemo012;

import java.util.concurrent.locks.ReentrantLock;

public class Ticket implements Runnable {
    //票的數量
    private int ticket = 100;
    private Object obj = new Object();
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            //synchronized (obj){//多個執行緒必須使用同一把鎖.
            try {
                lock.lock();
                if (ticket <= 0) {
                    //賣完了
                    break;
                } else {
                    Thread.sleep(100);
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "在賣票,還剩下" + ticket + "張票");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
            // }
        }
    }
}

package com.itheima.threaddemo012;

public class Demo {
    public static void main(String[] args) {
        /*Ticket ticket1 = new Ticket();
        Ticket ticket2 = new Ticket();
        Ticket ticket3 = new Ticket();

        Thread t1 = new Thread(ticket1);
        Thread t2 = new Thread(ticket2);
        Thread t3 = new Thread(ticket3);*/

        Ticket ticket = new Ticket();

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

        t1.setName("視窗一");
        t2.setName("視窗二");
        t3.setName("視窗三");

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

4.7 死鎖【瞭解】

1.什麼是死鎖?(吃飯)

執行緒死鎖是指由於兩個或者多個執行緒互相持有對方所需要的資源,導致這些執行緒處於等待狀態,無法前往執行

程式碼演示

 

 

 

5.生產者消費者

5.1 生產者和消費者思路分析【難點】

 

 

 

5.2生產者和消費者案例【難點】

Object類的等待和喚醒方法

方法名說明
void wait() 導致當前執行緒等待同時釋放鎖,直到另一個執行緒呼叫該物件的 notify()方法或 notifyAll()方法
void notify() 喚醒正在等待單個執行緒,並不立即釋放鎖
void notifyAll() 喚醒正在等待所有執行緒,並不立即釋放鎖

案例需求

  • 桌子類(Desk):定義表示包子數量的變數,定義鎖物件變數,定義標記桌子上有無包子的變數

  • 生產者類(Cooker):實現Runnable介面,重寫run()方法,設定執行緒任務

    1.判斷是否有包子,決定當前執行緒是否執行

    2.如果有包子,就進入等待狀態,如果沒有包子,繼續執行,生產包子

    3.生產包子之後,更新桌子上包子狀態,喚醒消費者消費包子

  • 消費者類(Foodie):實現Runnable介面,重寫run()方法,設定執行緒任務

    1.判斷是否有包子,決定當前執行緒是否執行

    2.如果沒有包子,就進入等待狀態,如果有包子,就消費包子

    3.消費包子後,更新桌子上包子狀態,喚醒生產者生產包子

  • 測試類(Demo):裡面有main方法,main方法中的程式碼步驟如下

    建立生產者執行緒和消費者執行緒物件

    分別開啟兩個執行緒

 

 

 

程式碼實現

package com.itheima.threaddemo014;

public class Desk {

    //定義一個標記
    //true 就表示桌子上有漢堡包的,此時允許吃貨執行
    //false 就表示桌子上沒有漢堡包的,此時允許廚師執行
    public static boolean flag = false;

    //漢堡包的總數量
    public static int count = 10;

    //鎖物件
    public static final Object lock = new Object();
}

package com.itheima.threaddemo014;

public class Cooker extends Thread {
//    生產者步驟:
//            1,判斷桌子上是否有漢堡包
//    如果有就等待,如果沒有才生產。
//            2,把漢堡包放在桌子上。
//            3,叫醒等待的消費者開吃。
    @Override
    public void run() {
        while(true){
            synchronized (Desk.lock){
                if(Desk.count == 0){
                    break;
                }else{
                    if(!Desk.flag){
                        //生產
                        System.out.println("廚師正在生產漢堡包");
                        Desk.flag = true;
                        Desk.lock.notifyAll();
                    }else{
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}

package com.itheima.threaddemo014;

public class Foodie extends Thread {
    @Override
    public void run() {
//        1,判斷桌子上是否有漢堡包。
//        2,如果沒有就等待。
//        3,如果有就開吃
//        4,吃完之後,桌子上的漢堡包就沒有了
//                叫醒等待的生產者繼續生產
//        漢堡包的總數量減一

        //套路:
            //1. while(true)死迴圈
            //2. synchronized 鎖,鎖物件要唯一
            //3. 判斷,共享資料是否結束. 結束
            //4. 判斷,共享資料是否結束. 沒有結束
        while(true){
            synchronized (Desk.lock){
                if(Desk.count == 0){
                    break;
                }else{
                    if(Desk.flag){
                        //有
                        System.out.println("吃貨在吃漢堡包");
                        Desk.flag = false;
                        Desk.lock.notifyAll();
                        Desk.count--;
                    }else{
                        //沒有就等待
                        //使用什麼物件當做鎖,那麼就必須用這個物件去呼叫等待和喚醒的方法.
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

    }
}

5.3生產者和消費者案例優化【難點】

  • 需求

    • 將Desk類中的變數,採用物件導向的方式封裝起來

    • 生產者和消費者類中構造方法接收Desk類物件,之後在run方法中進行使用

    • 建立生產者和消費者執行緒物件,構造方法中傳入Desk類物件

    • 開啟兩個執行緒

  • 程式碼實現

    package com.itheima.threaddemo015;
    
    public class Desk {
    
        //定義一個標記
        //true 就表示桌子上有漢堡包的,此時允許吃貨執行
        //false 就表示桌子上沒有漢堡包的,此時允許廚師執行
      //public static boolean flag = false;
        private boolean flag;
    
        //漢堡包的總數量
        //public static int count = 10;
        //以後我們在使用這種必須有預設值的變數
     // private int count = 10;
        private int count;
    
        //鎖物件
      //public static final Object lock = new Object();
        private final Object lock = new Object();
    
        public Desk() {
          this(false,10);
        }
    
        public Desk(boolean flag, int count) {
            this.flag = flag;
          this.count = count;
        }
    
        public boolean isFlag() {
          return flag;
        }
    
        public void setFlag(boolean flag) {
          this.flag = flag;
        }
    
        public int getCount() {
          return count;
        }
    
        public void setCount(int count) {
          this.count = count;
        }
    
        public Object getLock() {
          return lock;
        }
    
        @Override
        public String toString() {
            return "Desk{" +
                    "flag=" + flag +
                    ", count=" + count +
                    ", lock=" + lock +
                    '}';
      }
    }
    
    package com.itheima.threaddemo015;
    
    public class Cooker extends Thread {
    
        private Desk desk;
    
        public Cooker(Desk desk) {
            this.desk = desk;
        }
    //    生產者步驟:
    //            1,判斷桌子上是否有漢堡包
    //    如果有就等待,如果沒有才生產。
    //            2,把漢堡包放在桌子上。
    //            3,叫醒等待的消費者開吃。
    	
            //套路:
                //1. while(true)死迴圈
                //2. synchronized 鎖,鎖物件要唯一
                //3. 判斷,共享資料是否結束. 結束
                //4. 判斷,共享資料是否結束. 沒有結束
        @Override
        public void run() {
            while(true){
                synchronized (desk.getLock()){
                    if(desk.getCount() == 0){
                        break;
                    }else{
                        //System.out.println("驗證一下是否執行了");
                        if(!desk.isFlag()){
                            //生產
                            System.out.println("廚師正在生產漢堡包");
                            desk.setFlag(true);
                            desk.getLock().notifyAll();
                        }else{
                            try {
                                desk.getLock().wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
    }
    
    
    package com.itheima.threaddemo015;
    
    public class Desk {
    
        //定義一個標記
        //true 就表示桌子上有漢堡包的,此時允許吃貨執行
        //false 就表示桌子上沒有漢堡包的,此時允許廚師執行
        //public static boolean flag = false;
        private boolean flag;
    
        //漢堡包的總數量
        //public static int count = 10;
        //以後我們在使用這種必須有預設值的變數
       // private int count = 10;
        private int count;
    
        //鎖物件
        //public static final Object lock = new Object();
        private final Object lock = new Object();
    
        public Desk() {
            this(false,10);
        }
    
        public Desk(boolean flag, int count) {
            this.flag = flag;
            this.count = count;
        }
    
        public boolean isFlag() {
            return flag;
        }
    
        public void setFlag(boolean flag) {
            this.flag = flag;
        }
    
        public int getCount() {
            return count;
        }
    
        public void setCount(int count) {
            this.count = count;
        }
    
        public Object getLock() {
            return lock;
        }
    
        @Override
        public String toString() {
            return "Desk{" +
                    "flag=" + flag +
                    ", count=" + count +
                    ", lock=" + lock +
                    '}';
        }
    }
    
    package com.itheima.threaddemo015;
    
    public class Foodie extends Thread {
        private Desk desk;
    
        public Foodie(Desk desk) {
            this.desk = desk;
        }
    
        @Override
        public void run() {
    //        1,判斷桌子上是否有漢堡包。
    //        2,如果沒有就等待。
    //        3,如果有就開吃
    //        4,吃完之後,桌子上的漢堡包就沒有了
    //                叫醒等待的生產者繼續生產
    //        漢堡包的總數量減一
    
            //套路:
                //1. while(true)死迴圈
                //2. synchronized 鎖,鎖物件要唯一
                //3. 判斷,共享資料是否結束. 結束
                //4. 判斷,共享資料是否結束. 沒有結束
            while(true){
                synchronized (desk.getLock()){
                    if(desk.getCount() == 0){
                        break;
                    }else{
                        //System.out.println("驗證一下是否執行了");
                        if(desk.isFlag()){
                            //有
                            System.out.println("吃貨在吃漢堡包");
                            desk.setFlag(false);
                            desk.getLock().notifyAll();
                            desk.setCount(desk.getCount() - 1);
                        }else{
                            //沒有就等待
                            //使用什麼物件當做鎖,那麼就必須用這個物件去呼叫等待和喚醒的方法.
                            try {
                                desk.getLock().wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
    
        }
    }

5.4阻塞佇列基本使用【重點】

(共4點)

1.什麼是阻塞佇列?

阻塞佇列是一個在佇列基礎上又支援了兩個附加操作的佇列

2.附加的方法

put(anObject): 將引數放入佇列,如果放不進去會阻塞

take(): 取出第一個資料,取不到會阻塞

3.阻塞佇列繼承結構

 

 

 

4.常見BlockingQueue實現類

ArrayBlockingQueue: 底層是陣列,有界

LinkedBlockingQueue: 底層是連結串列,無界.但不是真正的無界,最大為int的最大值

程式碼示例

package com.itheima.threaddemo016;

import java.util.concurrent.ArrayBlockingQueue;

public class Demo {
    public static void main(String[] args) throws InterruptedException {
         // 建立阻塞佇列的物件,容量為 1
         ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1);

         // 儲存元素
         arrayBlockingQueue.put("漢堡包");

         // 取元素
         System.out.println(arrayBlockingQueue.take());
         System.out.println(arrayBlockingQueue.take()); // 取不到會阻塞

         System.out.println("程式結束了");
    }
}

5.5阻塞佇列實現等待喚醒機制【難點】

案例需求

  • 生產者類(Cooker):實現Runnable介面,重寫run()方法,設定執行緒任務

    1.構造方法中接收一個阻塞佇列物件

    2.在run方法中迴圈向阻塞佇列中新增包子

    3.列印新增結果

  • 消費者類(Foodie):實現Runnable介面,重寫run()方法,設定執行緒任務

    1.構造方法中接收一個阻塞佇列物件

    2.在run方法中迴圈獲取阻塞佇列中的包子

    3.列印獲取結果

  • 測試類(Demo):裡面有main方法,main方法中的程式碼步驟如下

    建立阻塞佇列物件

    建立生產者執行緒和消費者執行緒物件,構造方法中傳入阻塞佇列物件

    分別開啟兩個執行緒

 

 

 

程式碼實現

package com.itheima.threaddemo016;

import java.util.concurrent.ArrayBlockingQueue;

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue<String> bd = new ArrayBlockingQueue<>(1);
        Foodie f = new Foodie(bd);
        Cooker c = new Cooker(bd);
        f.start();
        c.start();
    }
}
package com.itheima.threaddemo016;

import com.itheima.threaddemo015.Desk;

import java.util.concurrent.ArrayBlockingQueue;

public class Cooker extends Thread {

    private ArrayBlockingQueue<String> bd;

    public Cooker(ArrayBlockingQueue<String> bd) {
        this.bd = bd;
    }
    @Override
    public void run() {
        while (true) {
            try {
                bd.put("漢堡包");
                System.out.println("廚師放入一個漢堡包");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
package com.itheima.threaddemo016;

import com.itheima.threaddemo015.Desk;

import java.util.concurrent.ArrayBlockingQueue;

public class Foodie extends Thread {
    private ArrayBlockingQueue<String> bd;

    public Foodie(ArrayBlockingQueue<String> bd) {
        this.bd = bd;
    }

    @Override
    public void run() {
        while (true) {
            try {
                String take = bd.take();
                System.out.println("吃貨將" + take + "拿出來吃了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

總結:

在java中如何實現等待和喚醒機制?

1.wait和notify()或notifyAll()

wait:讓執行緒等待,同時立即釋放鎖 --->sleep():讓執行緒休眠,但是不會釋放鎖

notify()或notifyAll(): 喚醒等待的執行緒,但是不會立即釋放鎖

2.阻塞佇列

put:存,如果隊例滿的話,會阻塞(等待)

take:取,如果取不到,會阻塞(等待)

 

 

 

                        擴充套件練習

題目1

編寫程式,建立兩個執行緒物件,一根執行緒迴圈輸出“播放背景音樂”,另一根執行緒迴圈輸出“顯示畫面”; 要求:

1: 1個執行緒使用Runnable介面的匿名內部類實現

2: 另一個執行緒使用lambda實現

效果:

 

 

 

參考答案:

package day13.No_1;

public class Demo {
   public static void main(String[] args) {
       //匿名內部類實現
       new Thread(new Runnable() {
           @Override
           public void run() {
               while (true) {
                   System.out.println("播放背景音樂!");
              }
          }
      }).start();
       //lambda實現
       new Thread(() -> {
           while (true) {
               System.out.println("顯示畫面!");
          }
      }).start();
  }
}

題目2

3.請使用繼承Thread類的方式定義一個執行緒類,在run()方法中迴圈10次,每1秒迴圈1次,每次迴圈按“yyyy-MM-dd HH:mm:ss”的格式列印當前系統時間。 請定義測試類,並定義main()方法,啟動此執行緒,觀察控制檯列印。

要求:

1: 使用匿名內部類配合SimpleDateFormat和Date實現

2: 使用lambda配合LocalDateTime和DateTimeFormatter實現

效果:

 

 

 

參考答案:

package day13.No_2;

import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;

public class Demo {
   public static void main(String[] args) {
 /*
 //方式一內部類實現
       new Thread(new Runnable() {
           @Override
           public void run() {
               for (int i = 0; i < 10; i++) {
                   try {
                       Thread.sleep(1000);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   Date date = new Date();
                   SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                   String format = sdf.format(date);
                   System.out.println(format);
               }
           }
       }).start();
       */
 //方式二:lambda實現
       new Thread(()->{
           for (int i = 0; i < 10; i++) {
               try {
                   Thread.sleep(1000);
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
               LocalDateTime now = LocalDateTime.now();
               DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
               String format = dateTimeFormatter.format(now);
               System.out.println(format);
          }
      }).start();
  }
}

 執行效果:

 

 

 

題目3

請編寫多執行緒應用程式,模擬多個人通過一個山洞: (1).這個山洞每次只能通過一個人,每個人通過山洞的時間為1秒; (2).建立10個執行緒,同時準備過此山洞,並且定義一個變數用於記錄通過隧道的人數。顯示每次通過山洞人的姓名,和通過順序;

要求:

保證安全問題,不能出現多個人同時通過山洞的現象;(必須逐一通過)

效果:

 

 

 

參考答案:

執行緒檔案:

package day13.No_3;

public class MyRunnable implements Runnable {
   private static int count = 10;
   private final static Object obj = new Object();
   private static int i = 1;

   @Override
   public void run() {
       synchronized (obj) {
           try {
               Thread.sleep(1000);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
           count--;
           System.out.println(Thread.currentThread().getName() + "通過山洞,他是第" + i + "個通過");
           i++;
      }
  }
}


測試檔案:

package day13.No_3;

public class Demo {
   public static void main(String[] args) {
     //此處可以使用for迴圈建立執行緒
       MyRunnable mr = new MyRunnable();
       Thread tr1 = new Thread(mr, "執行緒1");
       tr1.start();
       Thread tr2 = new Thread(mr, "執行緒2");
       tr2.start();
       Thread tr3 = new Thread(mr, "執行緒3");
       tr3.start();
       Thread tr4 = new Thread(mr, "執行緒4");
       tr4.start();
       Thread tr5 = new Thread(mr, "執行緒5");
       tr5.start();
       Thread tr6 = new Thread(mr, "執行緒6");
       tr6.start();
       Thread tr7 = new Thread(mr, "執行緒7");
       tr7.start();
       Thread tr8 = new Thread(mr, "執行緒8");
       tr8.start();
       Thread tr9 = new Thread(mr, "執行緒9");
       tr9.start();
       Thread tr10 = new Thread(mr, "執行緒10");
       tr10.start();

  }
}

 執行效果:

 

 

 

題目4

拼手速抽獎案例.

1.現有一個集合裝了10個獎品在裡面,分別是:{"電視機","電冰箱","電腦","遊戲機","洗衣機","空調","手機","平板電腦","電動車","電飯煲"};

2.假如有3個人同時去抽這10個獎品.最後列印出來.三個人各自都抽到了什麼獎品.

例如:

張三: “電視機”,”電冰箱”,”電腦”,”遊戲機”,”洗衣機”

李四: ”空調”,”手機”,”平板電腦”,

王五: ”電動車”,”電飯煲

要求:

1:3個人同時開始抽獎,每次抽獎需要使用0.5秒才能完成抽獎;

2:需要控制住同一個獎項不能同時被多個人抽走;

效果:

 

 

 

參考答案:

執行緒類

package day13.No_4;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class MyRunnable implements Runnable {
   private static ArrayList<String>list=new ArrayList<>(List.of("電視機","電冰箱","電腦","遊戲機","洗衣機","空調","手機","平板電腦","電動車","電飯煲"));
   private static Object object=new Object();
   @Override
   public void run() {
       while (list.size()>0){
           synchronized (object){
               if (list.size()==0){
                   return;
              }
               try {
                   Thread.sleep(500);
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
               String name = list.remove(new Random().nextInt(list.size()));
               System.out.println(Thread.currentThread().getName()+"抽到了"+name);
          }
      }
  }
}

測試類:

package day13.No_4;

public class Demo {
   public static void main(String[] args) {
       MyRunnable mr = new MyRunnable();
       Thread t1 = new Thread(mr, "張三");
       Thread t2 = new Thread(mr, "李四");
       Thread t3 = new Thread(mr, "王五");
       t1.start();
       t2.start();
       t3.start();
  }
}

執行效果:

 

 

 

 

 

相關文章