多執行緒(3)

忘記自我介紹發表於2020-09-23

1、執行緒通訊

生產者與消費者問題(英語:Producer-consumer problem),也稱有限緩衝問題(英語:Bounded-buffer problem),是一個多執行緒同步問題的經典案例。

該問題描述了兩個(多個)共享固定大小緩衝區的執行緒——即所謂的“生產者”和“消費者”——在實際執行時會發生的問題。生產者的主要作用是生成一定量的資料放到緩衝區中,然後重複此過程。與此同時,消費者也在緩衝區消耗這些資料。該問題的關鍵就是要保證生產者不會在緩衝區滿時加入資料,消費者也不會在緩衝區中空時消耗資料。

生產者與消費者問題中其實隱含了兩個問題:

● 執行緒安全問題:因為生產者與消費者共享資料緩衝區,不過這個問題可以使用同步解決。

● 執行緒的協調工作問題:

要解決該問題,就必須讓生產者執行緒在緩衝區滿時等待(wait),暫停進入阻塞狀態,等到下次消費者消耗了緩衝區中的資料的時候,通知(notify)正在等待的執行緒恢復到就緒狀態,重新開始往緩衝區新增資料。同樣,也可以讓消費者執行緒在緩衝區空時進入等待(wait),暫停進入阻塞狀態,等到生產者往緩衝區新增資料之後,再通知(notify)正在等待的執行緒恢復到就緒狀態。通過這樣的通訊機制來解決此類問題。

在這裡插入圖片描述

Object類中提供了wait()、notify()、notifyAll()方法,這三個方法並不屬於Thread類,那是因為這三個方法必須有同步監視器物件來呼叫,而同步監視器物件可以是任意型別的物件,因此它們只能宣告在Object類中。

案例:快餐店的廚師、服務員、取餐檯

1、一個生產者、一個消費者

案例:有加餐館的取餐口比較小,只能放10份快餐,廚師做完快餐放在取餐口的工作臺上,伺服器從這個工作臺取出快餐給顧客。現在有一個廚師和一個服務員。

package com.bdit.part04;
   
 public class Workbench {
      private static final int MAX_VALUE = 10;
      private int num;
      public synchronized void put() {
           if(num >= MAX_VALUE){
               try {
                    this.wait();
               } catch (InterruptedException e) {
                    e.printStackTrace();
               }
           }
           try {
               Thread.sleep(100);
               //加入睡眠時間是放大問題現象,去掉同步和wait等,可觀察問題
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           num++;
           System.out.println("廚師製作了一份快餐,現在工作臺上有:" + num + "份快餐");
           this.notify();
      }
   
      public synchronized void take() {
           if(num <=0){
               try {
                    this.wait();
               } catch (InterruptedException e) {
                    e.printStackTrace();
               }
           }
           try {
               Thread.sleep(100);
               //加入睡眠時間是放大問題現象,去掉同步和wait等,可觀察問題
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           num--;
           System.out.println("服務員取走了一份快餐,現在工作臺上有:" + num + "份快餐");
           this.notify();
      }
  }
package com.bdit.part04;

  public class Cook extends Thread{
      private Workbench workbench;
      
      public Cook(Workbench workbench) {
           super();
           this.workbench = workbench;
      }
   
      public void run(){
	   for (int i = 1; i <= 10; i++) {
               workbench.put();
           }
      }
  }
package com.bdit.part04;
   
 public class Waiter extends Thread{
      private Workbench workbench;
      
      public Waiter(Workbench workbench) {
           super();
           this.workbench = workbench;
      }
   
      public void run(){
 	   for (int i = 1; i <= 10;i++) {
               workbench.take();
           }
      }
  }
package com.bdit.part04;
   
 public class TestOneAndOne {
      public static void main(String[] args) {
           Workbench bench = new Workbench();
           Cook c = new Cook(bench);
           Waiter w = new Waiter(bench);
           
           c.start();
           w.start();
      }
  }

2、多個生產者與多個消費者

案例:有加餐館的取餐口比較小,只能放10份快餐,廚師做完快餐放在取餐口的工作臺上,伺服器從這個工作臺取出快餐給顧客。現在有多個廚師和多個服務員。

package com.bdit.part04;
   
  public class Workbench {
      private static final int MAX_VALUE = 10;
      private int num;
      public synchronized void put() {
           while(num >= MAX_VALUE){
               try {
                    this.wait();
               } catch (InterruptedException e) {
                    e.printStackTrace();
               }
           }
           try {
               Thread.sleep(100);
               //加入睡眠時間是放大問題現象,去掉同步和wait等,可觀察問題
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           num++;
           System.out.println("廚師製作了一份快餐,現在工作臺上有:" + num + "份快餐");
           this.notifyAll();
      }
      public synchronized void take() {
           while(num <=0){
               try {
                    this.wait();
               } catch (InterruptedException e) {
                    e.printStackTrace();
               }
           }
           try {
               Thread.sleep(100);
               //加入睡眠時間是放大問題現象,去掉同步和wait等,可觀察問題
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           num--;
           System.out.println("服務員取走了一份快餐,現在工作臺上有:" + num + "份快餐");
           this.notifyAll();
      }

廚師和服務員執行緒的程式碼不變

package com.bdit.part04;
   
 public class TestManyAndMany {
      public static void main(String[] args) {
           Workbench bench = new Workbench();
           Cook c1 = new Cook(bench);
           Cook c2 = new Cook(bench);
           Waiter w1 = new Waiter(bench);
           Waiter w2 = new Waiter(bench);
           
           c1.start();
           c2.start();
           w1.start();
           w2.start();
      }
  }

2、單例(單態)設計模式

單例設計模式,是軟體開發中最常用的設計模式之一,它是指某個類在整個系統中只能有一個例項物件可被獲取和使用的程式碼模式。例如:代表JVM執行環境的Runtime類。

通常有餓漢式和懶漢式兩種。

2.1、餓漢式單例設計模式

所謂餓漢式,就是在類初始化時,直接建立物件。

優勢:因為Java的類載入和初始化的機制可以保證執行緒安全,所以這類形式的單例設計模式不存線上程安全問題。

【Java載入類是通過一個ClassLoader類載入器來完成的】

劣勢:不管你暫時是否需要該例項物件,都會建立,使得類初始化時間加長。

1、直接例項化餓漢式(簡潔直觀)

  package com.bdit.part05;
   
  public class Singleton1 {
      public static final Singleton1 INSTANCE = new Singleton1();
      private Singleton1(){
           
      }
  }

2、列舉式(最簡潔)

 package com.bdit.part05;
 
 public enum Singleton2 {
      INSTANCE
  }

3、靜態程式碼塊餓漢式(適合複雜例項化)

package com.bdit.part05;
   
  import java.io.IOException;
  import java.util.Properties;
   
  public class Singleton3 {
      public static final Singleton3 INSTANCE;
      private String info;
      
      static{
           try {
               Properties pro = new Properties();
               
               pro.load(Singleton3.class.getClassLoader().getResourceAsStream("single.properties"));
               
               INSTANCE = new Singleton3(pro.getProperty("info"));
           } catch (IOException e) {
               throw new RuntimeException(e);
           }
      }
      
      private Singleton3(String info){
           this.info = info;
      }
   
      public String getInfo() {
           return info;
      }
  
      public void setInfo(String info) {
           this.info = info;
      }
  }

2.2、懶漢式單例設計模式

所謂懶漢式:即延遲建立物件,直到使用者來獲取這個物件時,再建立。

優勢:不用不建立,用時再建立

劣勢:有執行緒安全問題

1、有執行緒安全問題的形式

執行緒不安全(適用於單執行緒)

package com.bdit.part05;
   
  public class Singleton4 {
     
   	private static Singleton4 instance;
        private Singleton4(){
           
        }
      
        public static Singleton4 getInstance(){
            if(instance == null){
               
               try {
                    Thread.sleep(100);
               } catch (InterruptedException e) {
                    e.printStackTrace();
               }
               
               instance = new Singleton4();
           }
           return instance;
      }
  }
package com.bdit.part05;
   
  public class TestSingleton4 {
 	private static Singleton4 instance1;
  	private static Singleton4 instance2;
   
        public static void main(String[] args) {
           
           Thread t1 = new Thread(){
 		public void run(){
                    instance1 = Singleton4.getInstance();
               }
           };
           t1.start();
           
           Thread t2 = new Thread(){
 		public void run(){
                    instance2 = Singleton4.getInstance();
               }
           };
           t2.start();
           
           try {
               t1.join();
               t2.join();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println(instance1);
           System.out.println(instance2);
           System.out.println(instance1 == instance2);
      }
   
  }

2、無執行緒安全問題的形式(適用於多執行緒)

package com.bdit.part05;

  public class Singleton5 {
 	private static Singleton5 instance;
        private Singleton5(){
           
        }
      
        public static Singleton5 getInstance(){
           if(instance == null){
               synchronized (Singleton5.class) {
                    if(instance == null){
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        
                        instance = new Singleton5();
                    }
               }
           }
           return instance;
      }
  }

3、靜態內部類形式(適用於多執行緒)

package com.bdit.part05;
   
 public class Singleton6 {
      private Singleton6(){
           
      }
      private static class Inner{
           private static final Singleton6 INSTANCE = new Singleton6();
      }
      
      public static Singleton6 getInstance(){
           return Inner.INSTANCE;
      }
  }

3、容器類執行緒安全問題

JDK5.0之後,新增了java.util.concurrent這個包,其中包括了一些確保執行緒安全的容器類,如ConcurrentHashMap、CopyOnWriteArrayList、CopyOnWriteArraySet,它們在效率與安全性上取得了較好的平衡。

4、執行緒池

4.1、執行緒池思想概述

我們使用執行緒的時候就去建立一個執行緒,這樣實現起來非常簡便,但是就會存在一個問題:

如果併發的執行緒數量很多,並且每個執行緒都是執行一個時間很短的任務就結束了,這樣頻繁建立執行緒就會大大降低系統的效率,因為頻繁的建立執行緒和銷燬執行緒需要時間的。

那麼有沒有一種辦法使得執行緒可以重複使用,就是執行完一個任務,並不被銷燬,而是可以繼續執行其他的任務

在Java中可以通過執行緒池來達到這樣的效果。

4.2、執行緒池概念

● 執行緒池:其實就是一個容納多個執行緒的容器,其中的執行緒可以重複使用,省去了頻繁建立執行緒物件的操作,無需反覆建立執行緒而消耗過多資源。

● 由於執行緒池中有很多操作都是與優化資源相關的

在這裡插入圖片描述

● 合理利用執行緒池能夠帶來的好處:

  1. 降低資源消耗,減少了建立和銷燬執行緒的次數

  2. 提高響應速度,當任務到達時,任務可以不需要等待執行緒建立就可以立即執行

  3. 提高執行緒的可管理性,可以根據系統的承受能力,調整執行緒池中工作執行緒的數目,防止因為消耗過多的記憶體,而導致伺服器當機。

4.3、執行緒池的應用

java裡面執行緒池的父介面是java.util.concurrent. Executor,但是嚴格意義上講Executor並不是一個執行緒池,而只是一個執行執行緒的工具,真正的執行緒池介面是java.util.concurrent. ExecutorService

要配置一個執行緒池是比較複雜的,尤其是對執行緒池的原理不很清楚的情況下,很有可能配置的執行緒池不是較優的,因此在java.util.concurrent.Executors執行緒工廠類裡面提供了一些靜態工廠,生成一些常用的執行緒池,官方也建議使用Executors工廠類來建立執行緒池物件。

Executors類由個建立執行緒池的方法如下:
public static ExecutorService newFixedThreadPool(int nThreads):返回執行緒池物件,建立的是有界限的執行緒池,也就是執行緒池中的個數可以指定最大數量。

獲取到了一個執行緒池物件,那麼怎麼使用呢?
public Future<?> submit(Runnable task):獲取執行緒池中的某一個執行緒物件,並執行

Future介面:用來記錄執行緒任務執行完畢後產生的結果。

使用執行緒池的步驟:

  1. 建立執行緒池物件

  2. 建立Runnable介面子類物件

  3. 提交Runnable介面子類物件

  4. 關閉執行緒池(一般不做)

package com.bdit;
   
  import javax.swing.plaf.TableHeaderUI;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Executors;
   
  /**
   * 執行緒池
   */
  public class Demo11 {
   
      
  public static void main(String[] args) {
          //建立執行緒池物件
  	  ExecutorService service= Executors.newFixedThreadPool(2);
          //建立Runnable例項物件
  	  MyRunnable4 myRunnable4=new MyRunnable4();
          //從執行緒池中獲取執行緒物件,執行MyRunnable4中的run方法
  	  service.submit(myRunnable4);
          //再次呼叫
  	  service.submit(myRunnable4);
  	  service.submit(myRunnable4);
   
          //關閉執行緒池
  	  service.shutdown();
   
      }
  }
   
  class MyRunnable4 implements Runnable{
   
      @Override
      public void run() {
  	  System.out.println("我是一個執行緒");
          try {
  		Thread.sleep(1000);
          } catch(InterruptedException e) {
  		e.printStackTrace();
          }
	System.out.println(Thread.currentThread().getName()+",執行緒池");
      }
  }

相關文章