最詳細的java多線程教程來了

1. 線程概述

1.1 線程和進程

  • 進程是處於運行過程當中的程序,而且具備必定的獨立功能java

  • 併發性:同一個時刻只能有一條指令執行,但多個進程指令被快速輪換執行程序員

  • 並行:多條指令在多個處理器上同時執行web

  • 線程是進程的執行單元面試

1.2 多線程的優點

  • 進程之間不能共享內存,但線程之間很是容易編程

  • 系統建立進程時須要爲該進程從新分配系統資源,但建立線程則代價小得多,所以使用多線程效率更高安全

  • Java語言內置了多線程功能多線程

2. 線程建立與啓動

2.1 繼承Thread

 

 public class FirstThread extends Thread {
     private int i;
 ​
     @Override
     public void run() {
         for(i = 0; i < 50; i ++){
             System.out.println(this.getName() + "" + i);
         }
     }
 ​
     public static void main(String[] args){
         FirstThread ft = new FirstThread();
         for(int i =0; i < 100;i ++){
             System.out.println(Thread.currentThread().getName() + "" + i);
             if(i == 20) {
                 ft.run();
             }
         }
     }
 }

 

2.2 實現Runnable接口

 

 
public class FirstThread implements java.lang.Runnable {
     private int i;
 ​
     public void run() {
         for(i = 0; i < 50; i ++){
             System.out.println(Thread.currentThread().getName()+ "" + i);
         }
     }
 ​
     public static void main(String[] args){
         FirstThread ft = new FirstThread();
         for(int i =0; i < 100;i ++){
             System.out.println(Thread.currentThread().getName() + "" + i);
             if(i == 20) {
                 ft.run();
             }
         }
     }
 }

 

2.3 使用Callable和Future

  • Callable接口提供了一個call()方法能夠做爲線程執行體,call()方法有返回值且能夠聲明拋出異常併發

  • Java5提供了Future接口來表明Callable接口裏call()方法的返回值,併爲Future接口提供了一個FutureTask實現類ide

  • Future接口定義的方法:學習

方法名 做用
boolean cancel(boolean mayInterruptIfRunning) 試圖取消該Future裏關聯的Callable任務
V get() 返回Callable任務裏call方法的返回值,該方法會形成線程阻塞,等子線程執行完才能得到
V get(long timeout, TimeUnit unit) 返回Callable任務裏call方法的返回值。該方法讓程序最多阻塞timeoutunit指定的時間,若是通過指定時間Callable任務尚未返回值則拋出TimeoutException異常
boolean isCancelled() Callable中的任務是否取消
boolean isDone() Callable中的任務是否完成

 

 public class CallableDemo {
     public static void main(String[] args){
         FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)() -> {
             int i = 0;
             for( ; i < 100; i++){
                 System.out.println(i);
             }
             return i;
         });
         new Thread(task).start();
         try {
             System.out.println(task.get());
         } catch (InterruptedException e) {
             e.printStackTrace();
         } catch (ExecutionException e) {
             e.printStackTrace();
         }
     }
 }

 

2.4 建立線程的三種方式對比

RunnableCallable優劣勢:

  • 線程類只是實現了RunnableCallable接口,還能夠繼承其餘類

  • Runnable和Callable狀況下,多個線程能夠共享同一個target對象,因此很是適合多個相同線程來處理同一份資源的狀況

  • 編程稍稍複雜,若是須要訪問當前線程,則必須使用Thread.currentThread()

Thread優劣勢:

  • 線程類已經繼承了Thread類,因此不能再繼承其餘父類

  • 編寫簡單,若是須要訪問當前線程,用this使用

3. 線程生命週期

3.1 新建和就緒狀態

  • new語句僅僅由Java虛擬機爲其分配內存,並無表現出任何線程的動態特徵

  • 若是直接調用繼承類的run方法,則只會有MainActivity,並且不能經過getName得到當前執行線程的名字,而需用Thread.currentThread().getName()

  • 調用了run方法後,該線程已經再也不處於新建狀態

3.2 運行和阻塞狀態

  • 當線程數大於處理器數時,存在多個線程在同一個CPU上輪換的現象

  • 協做式調度策略:只有當一個線程調用了sleep()yield()方法纔會放棄所佔用的資源——即必須線程主動放棄所佔用的資源

  • 搶佔式調度策略:系統給每一個可執行的線程分配一個小的時間段來處理任務,當任務完成後,系統會剝奪該線程所佔用的資源

  • 被阻塞的線程會在合適的時候從新進入就緒狀態

img

線程狀態轉換圖

3.3 死亡狀態

  • 測試線程死亡可用isAlive()

  • 處於死亡的線程沒法再次運行,不然引起IllegalThreadStateException異常

 

「大清亡於閉關鎖國,學習技術須要交流和資料」。 在這裏我給你們準備了不少的學習資料免費獲取,包括但不限於java進階學習資料、技術乾貨、大廠面試題系列、技術動向、職業生涯等一切有關程序員的分享.java進階方法筆記,學習資料,面試題,電子書籍免費領取,讓你成爲java大神,追到本身的女神,走向人生巔峯

4. 控制線程

4.1 join線程

  • MainActivity調用了A.join(),則MainActivity被阻塞,A線程執行完後MainActivity才執行

4.2 後臺線程(Daemon Thread)

  • 若是全部的前臺線程都死亡,後臺線程會自動死亡

 

 

img

運行結果

4.3 線程睡眠sleep

 

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

 

  • sleep方法暫停當前線程後,會給其餘線程執行機會,不會理會其餘線程優先級;但yield方法只會給優先級相同或更高的線程

  • sleep方法將轉入阻塞狀態,直到通過阻塞時間纔會轉入就緒;yield強制當前線程轉入就緒狀態

  • sleep方法拋出了InterruptedException,yield方法沒拋出異常

4.4 改變線程優先級

  • 優先級高的線程得到較多的執行機會,優先級低的線程得到較少的執行機會

  • setPrioritygetPriority方法來設置和返回指定線程的優先級

5. 線程同步

  • run()方法不具備同步安全性

  • *java*引入了同步監視器來解決多線程同步問題,sychronized(obj)obj就是共享資源

5.1 同步方法

  • 同步方法就是使用synchronized來修飾某個方法

  • 實例方法的同步監視器默認是this

  • *Java*中不可變類老是線程安全的,可變類對象須要額外的方法來保證其線程安全

 

 public class DaemonThread extends Thread {
 ​
     static int balance = 100;
     int drawAmount;
     String name;
 ​
     public DaemonThread(int drawAmount, String name){
         this.drawAmount = drawAmount;
         this.name = name;
     }
 ​
     @Override
     public void run() {
         this.draw(drawAmount);
     }
 ​
     public synchronized void draw(int amount){
         if(balance  >= amount){
             System.out.println(this.name + "取出了" + amount);
             try{
                 Thread.sleep(1);
             } catch (InterruptedException e){
                 e.printStackTrace();
             }
             balance -= amount;
             System.out.println("\t餘額爲" + balance);
 ​
         } else{
             System.out.println(this.name + "取現失敗");
         }
     }
     public static void main(String[] args){
         new DaemonThread(50, "A").start();
         new DaemonThread(100, "B").start();
     }
 }

 

5.2 釋放同步監視器的鎖定

下列狀況下,線程會釋放對同步監視器的鎖定

  • 當前線程的同步方法、同步代碼塊執行結束

  • 遇到了breakreturn

  • 遇到異常

  • 程序執行了同步監視器對象的wait()方法

下列狀況下不會釋放:

  • 執行同步方法時,程序調用Thread.sleep() Thread.yield()方法

  • 其餘線程調用了該線程的suspend方法

5.3 同步鎖

  • *Java5*開始,提供了一種功能更強大的同步鎖機制,能夠經過顯式定義同步鎖對象來實現同步

  • Lock提供了比synchronized更普遍的鎖定操做,而且支持多個相關的Condition對象

  • Lock類型: Lock ReadWriteLock ReentrantLock:經常使用,能夠對一個加鎖的對象從新加鎖 ReentrantReadWriteLock StampedLock

方法名 做用
lock 加鎖
unlock 解鎖

5.4 死鎖

A等B,B等A

5.5 線程通訊

5.5.1 傳統的線程通訊

方法名 做用
wait 致使當前線程等待,直到其餘線程調用該同步監視器的notify()notifyAll()方法
notify 喚醒在此同步監視器等待的單個線程
notifyAll 喚醒在此同步監視器等待的全部線程
  • wait()必須在加鎖的狀況下執行

5.5.2 使用Condition

  • 若是系統中不適用synchronized來保證線程同步,而使用Lock對象來保證同步,那麼沒法使用waitnotifynotifyAll()來進行線程通訊

  • 當使用Lock對象,*Java*提供Condition保證線程協調

  • Condition方法以下

方法名 做用
await 致使當前線程等待,直到其餘線程調用該同步監視器的signal()signalAll()方法
signal 喚醒在此Lock對象的單個線程
signalAll 喚醒在此Lock對象的全部線程

5.5.3 使用阻塞隊列

  • *Java*提供了一個BlockingQueue接口

  • 當生產者線程試圖向BlockingQueue放入元素時,若是該隊列已滿,則該線程被阻塞;當消費者線程試圖從BlockingQueue取出元素時,若是該隊列已空,則該線程被阻塞

方法名 做用
put(E e) 嘗試把E元素放入BlockingQueue
take() 嘗試從BlockingQueue的頭部取出元素

 

 
public class BlockingQueueThread extends Thread {
 ​
     private BlockingQueue<String> bq;
 ​
     public BlockingQueueThread(BlockingQueue<String> bq){
         this.bq = bq;
     }
 ​
     @Override
     public void run() {
         String[] strColl = new String[]{
                 "Java",
                 "Kotlin",
                 "JavaScript"
         };
 ​
 ​
 ​
         for(int i = 0; i < 1000; i ++){
             try {
                 System.out.println(getName() + "開始動工" + i);
                 bq.put(strColl[i % 3]);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
 ​
         System.out.println(getName() + "工做結束");
     }
 ​
     public static void main(String[] args){
         BlockingQueue<String> bq = new ArrayBlockingQueue<>(5);
         new BlockingQueueThread(bq).start();
     }
 }

 

img

結果展現

 

能夠看到,當Thread-0運行到第6次時就已經被阻塞,不能往裏添加內容

 

「大清亡於閉關鎖國,學習技術須要交流和資料」。 在這裏我給你們準備了不少的學習資料免費獲取,包括但不限於java進階學習資料、技術乾貨、大廠面試題系列、技術動向、職業生涯等一切有關程序員的分享.java進階方法筆記,學習資料,面試題,電子書籍免費領取,讓你成爲java大神,追到本身的女神,走向人生巔峯

6. 線程組和未處理的異常

  • ThreadGroup表示線程組,能夠對一批線程進行分類管理

  • 子線程和建立它的父線程在同一個線程組內

  • ThreadGroup方法

方法名 做用
int activeCount 返回線程組中活動線程的數目
interrupt 中斷此線程組中全部活動線程的數目
isDaemon 線程組是不是後臺線程組
setDaemon 設置後臺線程
setMaxPriority 設置線程組的最高優先級

7. 線程池

  • 線程池在系統啓動時即建立大量空閒的線程

  • 程序將一個Runnable對象或Callable對象傳給線程池,線程池就會啓動一個空閒線程來執行他們

  • 線程結束不死亡,而是回到空閒狀態

  • *Java8*以後新增了一個Executors工廠類來生產線程池

7.1 ThreadPool

 

 public class ThreadPoolTest {
 ​
     public static void main(String[] args){
         ExecutorService pool = Executors.newFixedThreadPool(2);
 ​
         java.lang.Runnable target = () -> {
            for (int i = 0; i < 100 ; i ++){
                System.out.println(Thread.currentThread().getName() + "的i爲" +i);
            }
         };
 ​
         pool.submit(target);
         pool.submit(target);
         pool.shutdown();
     }
 }

 

img

結果展現

7.2 ForkJoinPool

  • 將一個任務拆分紅多個小任務並行計算,再把多個小任務的結果合併成總的計算結果

  • ForkJoinPoolExecutorService的實現類

 

 
public class PrintTask extends RecursiveAction {
 ​
     public static int THREADSH_HOLD = 50;
 ​
     private int start;
 ​
     private int end;
 ​
     public PrintTask(int start, int end){
         this.start = start;
         this.end = end;
     }
 ​
     @Override
     protected void compute() {
         if(end - start < THREADSH_HOLD){
             for(int i = start; i < end; i ++){
                 System.out.println(Thread.currentThread().getName() + "的i爲" + i);
             }
         } else {
             PrintTask left = new PrintTask(start, (start + end) / 2);
             PrintTask right = new PrintTask((start + end) / 2 , end);
             left.fork();
             right.fork();
         }
     }
 ​
     public static void main(String[] args) throws InterruptedException {
         PrintTask printTask = new PrintTask(0 , 300);
         ForkJoinPool pool = new ForkJoinPool();
         pool.submit(printTask);
         pool.awaitTermination(2, TimeUnit.SECONDS);
         pool.shutdown();
 ​
     }
 }

 

img

 

「大清亡於閉關鎖國,學習技術須要交流和資料」。 在這裏我給你們準備了不少的學習資料免費獲取,包括但不限於java進階學習資料、技術乾貨、大廠面試題系列、技術動向、職業生涯等一切有關程序員的分享.java進階方法筆記,學習資料,面試題,電子書籍免費領取,讓你成爲java大神,追到本身的女神,走向人生巔峯

相關文章
相關標籤/搜索