Java多線程01(Thread類、線程建立、線程池)

Java多線程(Thread類、線程建立、線程池)

第一章 多線程

1.1 多線程介紹

1.1.1 基本概念

  • 進程:進程指正在運行的程序。確切的來講,當一個程序進入內存運行,即變成一個進程,進程是處於運行過程當中的程序,而且具備必定獨立功能。
  • 線程:線程是進程中的一個執行單元,負責當前進程中程序的執行,一個進程中至少有一個線程。一個進程中是能夠有多個線程的,這個應用程序也能夠稱之爲多線程程序。
  • 簡而言之:一個程序運行後至少有一個進程,一個進程中能夠包含多個線程

1.1.2 單線程程序

  • 從入口main到結束,一條路走到底
    • 好處:沒有安全隱患
    • 缺點:效率低
    • 解決缺點:讓方法一同運行起來,爲程序開啓多個執行的路,每一個執行的路,成爲線程。
    • 示例代碼:
    public static void main(String[] args){
        add();
        remove();
        get();
        System.out.println(222);
    }
    
    public static void add(){
        // 一萬次循環
    }
    public static void remove(){
    }
    public static void get(){
    }

1.1.3 深刻理解多線程

  • CPU中央處理器
    • Inter AMD
    • Inter Core i7 6680M
    • 四核心,八線程
    • 執行多線程時,每一個功能均可以單獨執行,開啓功能,對CPU開啓新的執行路徑。
    • 線程深刻理解:每一個功能對於CPU的獨立執行路徑,就是線程。
  • 網上下載一個軟件:
    • 下載的流程:
    • 單線程下載:一次讀取n個字節
      • 瀏覽器下載:(IE)寫一個,讀一個,寫一個,讀一個
    • 多線程下載:一次讀取m*m個字節,多(m)個線程讀取同一個文件
      • 迅雷:開啓一個線程,讀取其中的一部分,再開啓一個線程再讀取一部分,...

1.2 程序運行原理

  • 分時調度:
    • 全部線程輪流使用 CPU 的使用權,平均分配每一個線程佔用 CPU 的時間。
  • 搶佔式調度:
    • 優先讓優先級高的線程使用 CPU,若是線程的優先級相同,那麼會隨機選擇一個(線程隨機性),Java使用的爲搶佔式調度。

搶佔式調度詳解

大部分操做系統都支持多進程併發運行,如今的操做系統幾乎都支持同時運行多個程序。好比:如今咱們上課一邊使用編輯器,一邊使用錄屏軟件,同時還開着畫圖板, dos窗口等軟件。此時,這些程序是在同時運行,」感受這些軟件好像在同一時刻運行着「。
實際上,CPU(中央處理器)使用搶佔式調度模式在多個線程間進行着高速的切換。對於CPU的一個核而言,某個時刻,只能執行一個線程,而 CPU的在多個線程間切換速度相對咱們的感受要快,看上去就是在同一時刻運行。
其實,多線程程序並不能提升程序的運行速度,但可以提升程序運行效率,讓CPU的使用率更高。java

1.3 主線程

  • 代碼示例:瀏覽器

    public class Demo {
        public static void main(String[] args) {
        fun();
        System.out.println(Math.abs(-9));
    }
    public static void fun() {
        for(int i=0;i<10000; i++) {
            System.out.println(i);
        }
    }
    }
  • 分析:
    • 程序:從上到下的執行過程
    • 在dos窗口中:java Demo
    • 啓動JVM,運行Demo.main
    • JVM 運行方法main,操做系統開啓線程
    • 對於CPU有了一個執行的路徑,運行方法main路徑,有個名字「main」
    • 主線程:
  • 思考:
    • 可否實現一個主線程負責執行其中一個循環,再由另外一個線程負責其餘代碼的執行,最終實現多部分代碼同時執行的效果?
    • 可以實現同時執行,經過Java中的多線程技術來解決該問題。

1.4 Thread類

  • 經過API中搜索,查到Thread類。經過閱讀Thread類中的描述。Thread是程序中的執行線程。Java 虛擬機容許應用程序併發地運行多個執行線程。
  • 線程是程序中執行的線程。 Java虛擬機容許應用程序同時運行多個執行線程。
  • 每一個線程都有優先權。 具備較高優先級的線程優先於具備較低優先級的線程執行。 每一個線程可能也可能不會被標記爲守護進程。
  • 當在某個線程中運行的代碼建立一個新的Thread對象時,新線程的優先級最初設置爲等於建立線程的優先級,而且當且僅當建立線程是守護進程時纔是守護進程線程。安全

  • 經常使用構造方法多線程

    Thread()
        Allocates a new Thread object. 
    Thread(Runnable target) 
        Allocates a new Thread object. 
    Thread(Runnable target, String name)
        Allocates a new Thread object. 
    Thread(String name) 
        Allocates a new Thread object.
  • 經常使用方法併發

    • void run()
      • If this thread was constructed using a separate Runnable run object, then that Runnable object's run method is called; otherwise, this method does nothing and returns.
    • static void sleep(long millis)
      • Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers.
    • void start()
      • Causes this thread to begin execution; the Java Virtual Machine calls the run method of this thread.
  • 建立新執行線程有兩種方法:
    • 一種方法是將類聲明爲 Thread 的子類。該子類應重寫 Thread 類的 run 方法。建立對象,開啓線程。run方法至關於其餘線程的main方法。異步

      ```
        class PrimeThread extends Thread {
            long minPrime;
            PrimeThread(long minPrime) {
                this.minPrime = minPrime;
            }
            public void run() {
                // compute primes larger than minPrime
                . . .
            }
        }

      ```jvm

    • 另外一種方法是聲明一個實現 Runnable 接口的類。該類而後實現 run 方法。而後建立Runnable的子類對象,傳入到某個線程的構造方法中,開啓線程。編輯器

      class PrimeRun implements Runnable {
              long minPrime;
              PrimeRun(long minPrime) {
                  this.minPrime = minPrime;
              }
              public void run() {
                  // compute primes larger than minPrime
                  . . .
              }
          }

1.5 建立新執行線程方法一:繼承Thread類

  • 建立線程的步驟:
    1. 定義一個類繼承Thread。
    2. 重寫run方法。
    3. 建立子類對象,就是建立線程對象。
    4. 調用start方法,開啓線程並讓線程執行,同時還會告訴jvm去調用run方法。
  • 代碼示例:ide

    public class SubThread extends Thread {
        @Override
        public void run() {
            for(int i=0; i<50; i++) {
                System.out.println("run方法中的變量:"+i);
            }       
        }
    }
    
    public class ThreadDemo {   
        public static void main(String[] args) {   // 一、JVM從入口main開始執行主線程,從CPU開啓第一條線程路徑,執行main()方法。
            SubThread subThread = new SubThread();    // 二、建立線程對象,至關於開啓了一個新的線程,從CPU開啓第二條線程路徑,執行run()方法。
            subThread.start();   // 三、執行start方法時,調用run()方法,與main()方法同時要調用CPU,兩個執行路徑都會被CPU執行,CPU本身選擇的權利,出現執行結果,隨機性結果。
            for(int i=0; i<50; i++) {
                System.out.println("main方法中的變量:"+i);
            }
        }
    }

1.5.1 繼承Thread類原理

  • 思考1:線程對象調用 run方法和調用start方法區別?
    • 線程對象調用run方法不開啓線程。僅是對象調用方法。線程對象調用start開啓線程,並讓jvm調用run方法在開啓的線程中執行。
  • 思考2:咱們爲何要繼承Thread類,並調用其的start方法才能開啓線程呢?
    • 繼承Thread類:由於Thread類用來描述線程,具有線程應該有功能。
  • 思考3:那爲何不直接建立Thread類的對象呢?以下代碼:
    Thread t1 = new Thread();t1.start();
    • 這樣作沒有錯,可是該start調用的是Thread類中的run方法,而這個run方法沒有作什麼事情,更重要的是這個run方法中並無定義咱們須要讓線程執行的代碼。
  • 思考4:建立線程的目的是什麼?
    • 是爲了創建程序單獨的執行路徑,讓多部分代碼實現同時執行。也就是說線程建立並執行須要給定線程要執行的任務。
    • 對於以前所講的主線程,它的任務定義在main函數中。自定義線程須要執行的任務都定義在run方法中。
  • 思考5:爲何要重寫run方法
    • Thread類run方法中的任務並非咱們所須要的,只有重寫這個run方法。既然Thread類已經定義了線程任務的編寫位置(run方法),那麼只要在編寫位置(run方法)中定義任務代碼便可。因此進行了重寫run方法動做。

1.5.2 線程運行時的內存狀況

  • 單線程:全部執行的方法都在一個棧中,後調用的方法先執行,執行結束後彈出;
  • 多線程:每調用一個線程,就分配一個棧區,例如:main和run方法分別屬於不一樣的棧區,CPU執行時,隨機選擇執行一個棧區中的方法。

1.5.3 獲取線程名字Thread類方法getName()

  • Java API 中函數

    String getName()  // 返回線程的名字 
    static Thread currentThread()  // 獲取當前線程
  • 示例:

    public class NameThread extends Thread {
        @Override
        public void run() {
            System.out.println(super.getName());  // 輸出本線程的名字
        }
    }
    
    /*
     * 每一個線程都有本身的名字
     * 運行方法main線程的名字是"main"
     * 其餘線程也有名字,默認爲"Thread-0","Thread-1", ...
     * 得到主線程的名字的方法:JVM開啓主線程,運行方法main,主線程也是線程,是線程必然是Thread類的對象,Thread類中的靜態方法:
     *  static Thread currentThread() 返回正在執行的線程對象
     *  該對象調用getName方法,獲取線程名字
     */
    public class ThreadDemo2 {
        public static void main(String[] args) {
            NameThread nameThread = new NameThread();
            nameThread.start();
    
            // 獲取主線程的線程名
            System.out.println(Thread.currentThread().getName());
        }
    }

1.5.3 設置線程名字Thread類方法

  • Java API 中

    Thread(String name)  // 分配一個新的名字爲name的對象
    void setName(String name) // 改變線程的名字爲name
  • 方法一:

    // 在主線程中
    NameThread nameThread = new NameThread();
    nameThread.setName("Thread線程名字");
  • 方法二:

    // 在建立的線程類中的run方法中,調用Thread父類構造函數
    super("Thread線程名字");

1.5.4 Thread類方法sleep()

  • 位置:能夠寫在main方法中,也能夠寫在Thread類中。
  • 代碼:

    public class SleepThread extends Thread{    
        @Override
        public void run() {
            for(int i=0; i<5; i++) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(i);
            }
        }
    }
    
    public class ThreadDemo {
        public static void main(String[] args) throws InterruptedException {
            SleepThread sleepThread = new SleepThread();
            sleepThread.start();
            Thread.sleep(2000);
        }
    }

1.6 建立新執行線程方法二:實現Runnable接口

建立線程的另外一種方法是聲明實現 Runnable 接口的類。該類而後實現 run 方法。而後建立Runnable的子類對象,傳入到某個線程的構造方法中,開啓線程。
爲什麼要實現Runnable接口,Runable是啥玩意呢?繼續API搜索。
查看Runnable接口說明文檔:Runnable接口用來指定每一個線程要執行的任務。包含了一個 run 的無參數抽象方法,須要由接口實現類重寫該方法。

  • Java API 中

    // Runnable接口中只有一個run方法
    // 方法摘要
    void run() // 當使用實現接口Runnable的對象來建立線程時,啓動該線程會致使在該單獨執行的線程中調用該對象的run方法。
    
    // 建立線程使用Thread類中的構造方法:
    Thread(Runnable target)  // 分配一個線程對象,參數爲Runnable接口實現類的對象
  • 建立線程的步驟。
    1. 定義類實現Runnable接口。
    2. 覆蓋接口中的run方法。。
    3. 建立Thread類的對象
    4. 將Runnable接口的子類對象做爲參數傳遞給Thread類的構造函數。
    5. 調用Thread類的start方法開啓線程。
  • 示例:

    public class SubRunnable implements Runnable{
        @Override
        public void run() {
            for(int i=0; i<50; i++) {
                System.out.println("run..."+i);
            }       
        }
    }
    
    /**
     * 實現接口方式的線程
     * 建立Thread類對象,構造方法中,傳遞Runnable接口實現類對象
     * 調用Thread類的方法start
     */
    public class RunnableDemo {
        public static void main(String[] args) {
            SubRunnable subRunnable = new SubRunnable();
            Thread t = new Thread(subRunnable);
            t.start();  
            for(int i=0; i<50; i++) {
                System.out.println("main..."+i);
            }
        }
    }

1.6.1 實現Runnable接口的原理

  • 思考:爲何須要定一個類去實現Runnable接口呢?繼承Thread類和實現Runnable接口有啥區別呢?
    • 實現Runnable接口,避免了繼承Thread類的單繼承侷限性。覆蓋Runnable接口中的run方法,將線程任務代碼定義到run方法中。
    • 建立Thread類的對象,只有建立Thread類的對象才能夠建立線程。線程任務已被封裝到Runnable接口的run方法中,而這個run方法所屬於Runnable接口的子類對象,因此將這個子類對象做爲參數傳遞給Thread的構造函數,這樣,線程對象建立時就能夠明確要運行的線程的任務。

1.6.2 實現Runnable的好處

  • 避免侷限性、解耦合、資源共享
  • 第二種方式實現Runnable接口避免了單繼承的侷限性,因此較爲經常使用。實現Runnable接口的方式,更加的符合面向對象,線程分爲兩部分,一部分線程對象,一部分線程任務。繼承Thread類,線程對象和線程任務耦合在一塊兒。一旦建立Thread類的子類對象,既是線程對象,有又有線程任務。實現runnable接口,將線程任務單獨分離出來封裝成對象,類型就是Runnable接口類型。Runnable接口對線程對象和線程任務進行解耦。

1.7 線程的匿名內部類使用

  • 使用線程的內匿名內部類方式,能夠方便的實現每一個線程執行不一樣的線程任務操做。
    • 方式1:建立線程對象時,直接重寫Thread類中的run方法
    • 方式2:使用匿名內部類的方式實現Runnable接口,從新Runnable接口中的run方法

1.8 線程狀態(6種)

  • 線程狀態。 線程能夠處於如下狀態之一:
    • new:還沒有啓動的線程處於此狀態。
    • runnable:在Java虛擬機中執行的線程處於此狀態。
    • blocked:被阻塞等待監視器鎖定的線程處於此狀態。
    • wait:無限期等待另外一個線程執行特定操做的線程處於此狀態。
    • timed_waiting:正在等待另外一個線程執行最多指定等待時間的操做的線程處於此狀態。
    • terminated:已退出的線程處於此狀態。
  • 線程在給定時間點只能處於一種狀態。 這些狀態是虛擬機狀態,不反映任何操做系統線程狀態。

線程狀態圖:

第二章 線程池

2.1 線程池概念

2.1.1 線程池,其實就是一個容納多個線程的容器,其中的線程能夠反覆使用,省去了頻繁建立線程對象的操做,無需反覆建立線程而消耗過多資源。

線程池示意圖:

2.1.2 爲何要使用線程池?

- 在java中,若是每一個請求到達就建立一個新線程,開銷是至關大的。在實際使用中,建立和銷燬線程花費的時間和消耗的系統資源都至關大,甚至可能要比在處理實際的用戶請求的時間和資源要多的多。除了建立和銷燬線程的開銷以外,活動的線程也須要消耗系統資源。若是在一個JVM裏建立太多的線程,可能會使系統因爲過分消耗內存或「切換過分」而致使系統資源不足。爲了防止資源不足,須要採起一些

辦法來限制任何給定時刻處理的請求數目,儘量減小建立和銷燬線程的次數,特別是一些資源耗費比較大的線程的建立和銷燬,儘可能利用已有對象來進行服務。
- 線程池主要用來解決線程生命週期開銷問題和資源不足問題。經過對多個任務重複使用線程,線程建立的開銷就被分攤到了多個任務上了,並且因爲在請求到達時線程已經存在,因此消除了線程建立所帶來的延遲。這樣,就能夠當即爲請求服務,使用應用程序響應更快。另外,經過適當的調整線程中的線程數目能夠防止出現資源不足的狀況。

2.1.3 線程池原理

  • 本身建立線程池(僞代碼示例)

    ArrayList<Thread> threads = new ArrayList<Thread>();
    threads.add(new Thread());
    threads.add(new Thread());
    threads.add(new Thread());
    threads.add(new Thread());
    threads.add(new Thread());
    threads.add(new Thread());
    threads.add(new Thread());
    threads.add(new Thread());
    threads.add(new Thread());
    threads.add(new Thread());
    
    // 程序一開始的時候,建立多個線程對象,存儲到集合中,須要線程,從集合中獲取線程出來
    Thread t = threads.remove(0);
    // 使用線程
    t.start();
    // 線程用完,回到容器中繼續等待使用
    threads.add(t);
  • 從JDK5開始,內置線程池技術,不須要本身建立,直接使用便可。

2.2 使用線程池

2.2.1 使用線程池方式--Runnable接口

  • 一般,線程池都是經過線程池工廠建立,再調用線程池中的方法獲取線程,再經過線程去執行任務方法。
    • Executors:線程池建立工廠類
    • public static ExecutorService newFixedThreadPool(int nThreads):返回線程池對象
    • ExecutorService:線程池類
    • Future<?> submit(Runnable task):獲取線程池中的某一個線程對象,並執行
    • Future接口:用來記錄線程任務執行完畢後產生的結果。線程池建立與使用
  • 使用線程池中線程對象的步驟:
    • 建立線程池對象
    • 建立Runnable接口子類對象
    • 提交Runnable接口子類對象
    • 關閉線程池
  • 示例:

    public class ThreadPoolRunnable implements Runnable{
        @Override
        public void run() {     
            System.out.println(new Thread().getName());
        }
    }
    
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    /*
     * JDK1.5以後的新特性,實現線程池程序
     *  一、使用工廠類,Executors中的靜態方法建立線程對象,指定線程個數
     *  二、static ExecutorsService newFixedThreadPool(int 線程個數)   返回線程池對象
     *  三、返回的是 ExecutorsService接口的實現類(線程池對象)
     *  四、接口實現類對象,調用方法submit(Runnable r) 提交線程,執行任務
     */
    public class ThreadPoolDemo {
        public static void main(String[] args) {
            // 調用工廠類的靜態方法,建立線程池對象
            // Executors.newFixedThreadPool(2);返回線程池對象,是接口的實現類對象
            ExecutorService es = Executors.newFixedThreadPool(2);
            // 調用接口實現類對象es的方法submit,提交一個線程任務
            es.submit(new ThreadPoolRunnable());
            es.submit(new ThreadPoolRunnable());
            es.submit(new ThreadPoolRunnable());
        }
    }
  • Runnable接口的缺陷
    • 線程運行完沒有結果
    • 不能拋出異常

2.2.2 使用線程池方式—Callable 接口

  • Callable接口與Runnable接口功能類似,用來指定線程的任務。其中的call()方法,用來返回線程任務執行完畢後的結果,call方法可拋出異常。
    • ExecutorService:線程池類
    • <T> Future<T> submit(Callable<T> task):獲取線程池中的某一個線程對象,並執行線程中的call()方法
    • Future接口:用來記錄線程任務執行完畢後產生的結果。線程池建立與使用
  • 使用線程池中線程對象的步驟:
    • 建立線程池對象
    • 建立Callable接口子類對象
    • 提交Callable接口子類對象
    • 關閉線程池
  • 示例:

    import java.util.concurrent.Callable;
    
    public class ThreadPoolCallable implements Callable<String>{
        @Override
        public String call() throws Exception {
            return "abc";
        }
    }
    
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    public class ThreadPoolDemo {
        public static void main(String[] args) throws InterruptedException, ExecutionException {
            ExecutorService es = Executors.newFixedThreadPool(2);
            // 提交任務的方法,返回Future接口的實現類
            Future<String> f = es.submit(new ThreadPoolCallable());
            String s = f.get();
            System.out.println(s);
        }
    }

2.3 線程池練習:返回多個數相加的結果

```
import java.util.concurrent.Callable;
/*
 * 多線程的異步計算
 * 
 */
public class GetSumCallable implements Callable<Integer>{
    private int a;
    public GetSumCallable(int a) {
        this.a = a;
    }
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for(int i=0; i<=a; i++) {
            sum += i;
        }
        return sum;
    }   
}

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ThreadPoolDemo {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService es = Executors.newFixedThreadPool(2);
        Future<Integer> f1 = es.submit(new GetSumCallable(100));  // 加法
        Future<Integer> f2 = es.submit(new GetSumCallable(200));  // 減法
        System.out.println("1+2+...+100 = "+f1.get());
        System.out.println("1+2+...+200 = "+f2.get());
        es.shutdown();
    }
}
```

第三章 總結

建立線程的方式

  • 方式1,繼承Thread線程類
    • 步驟
      • 自定義類繼承Thread類
      • 在自定義類中重寫Thread類的run方法
      • 建立自定義類對象(線程對象)
      • 調用start方法,啓動線程,經過JVM,調用線程中的run方法
  • 方式2,實現Runnable接口
    • 步驟
      • 建立線程任務類 實現Runnable接口
      • 在線程任務類中 重寫接口中的run方法
      • 建立線程任務類對象
      • 建立線程對象,把線程任務類對象做爲Thread類構造方法的參數使用
      • 調用start方法,啓動線程,經過JVM,調用線程任務類中的run方法
  • 方式3,實現Callable接口
    • 步驟
      • 工廠類Executors靜態方法newFixedThreadPool方法,建立線程對象
      • 線程池對象ExecutorService接口實現類,調用方法submit提交線程任務:submit(Callable c)
相關文章
相關標籤/搜索