6.Java中的多線程與異常

1.異常

根類Throwable體系:java

  • Error:嚴重錯誤,程序自身已經不能處理的問題,出現的嚴重錯誤程序終止運行
  • Exception:編譯期異常,這種異常是強制咱們使用catch捕獲處理或throws拋出給調用者。你遇到這種異常必須進行catch或throws,若是不處理,編譯器會報錯。
    • RuntimeExeption:Exception的子類,運行時異常,這種異常咱們不須要處理,徹底由虛擬機接管。
      • NullPointerException:RuntimeExeption的子類,空指針異常

異常處理的五個關鍵字:try,catch,finally,throw,throws數據庫

(1)拋出異常 throw:

語法:throw new 異常類名(參數)數組

例子:throw new NullPointerException(「要訪問的數組不存在」)安全

注意:多線程

  • throw關鍵字必須放在方法的內部
  • throw關鍵字後邊new的對象必須是Exception或Exception子類對象
  • throw關鍵字拋出指定的異常對象,咱們就必須處理這個異常對象
    • throw關鍵字後邊建立的是RuntimeException或RuntimeException的子類對象,咱們能夠不處理默認交給JVM處理(打印異常對象,中斷程序)
    • throw關鍵字後邊建立的是編譯異常,咱們處理這個異常,要麼throws,要麼try...catch

Objects的非空判斷:併發

  Objects.requireNonNull(obj,message),用來判斷obj是否爲空,爲空則拋出異常,信息爲messageide

(2)throws關鍵字:異常處理的第一種方式,交給別人處理

做用:將異常拋出給方法的調用者處理,最終交給JVM處理-->中斷處理工具

使用格式:在方法聲明時使用測試

修飾符 返回值類型 方法名(參數列表) throws 異常1,異常2...{ throw new 異常1("xxx"); throw new 異常2("xxx"); ... }

注意:ui

  • 必須寫在方法聲明處
  • 聲明的異常必須是Exception或Exception子類對象
  • 方法內部拋出多個異常,聲明處必須也要聲明多個異常,若是拋出父異常也拋出子異常則只要聲明父異常便可
  • 咱們必須處理聲明的異常
    • 要麼交給方法調用者處理,最終交給JVM
    • 要麼使用try...catch本身處理異常

(3)如何獲取異常信息

Throwable中定義了一些查看異常的方法:

  • public String getMessage():獲取異常描述信息
  • public string toString():獲取異常的類型和異常描述信息
  • public void printStackTrace():打印異常的跟蹤棧信息並輸出到控制檯

(4)try,catch,finally

語法:

try{ 可能出現異常的代碼 }catch(異常類1 變量名){ 異常處理的邏輯 } ... catch(異常類n 變量名){ }finally{ 不管是否出現異常都會執行 }

注意:

  • finally不能單獨使用,必須和try一塊兒使用
  • finally通常用於資源釋放
  • 若是finally中有return則永遠返回finally中的結果,咱們須要避免出現return。
  • 當一個catch捕獲來處理異常後就不會調用其餘catch處理異常了
  • 多個catch捕獲異常,咱們先進行子異常捕獲處理,若是沒有子異常咱們就進行父異常捕獲處理

(5)異常注意事項

  • 多個異常分別處理:多個try..catch 處理
  • 多個異常一次捕獲,屢次處理:一個try多個catch處理,子異常在前,父異常在後
  • 多個異常一次捕獲,一次處理:一個try...catch,異常爲全部異常的父類或自己
  • 子父類異常:
    • 若是父類拋出多個異常,子類重寫父類方法時,拋出和父類相同的異常或者是父類異常的子類或者不拋出異常
    • 父類方法沒有拋出異常,子類重寫父類方法時也可不拋出異常,此時子類產生異常,只能捕獲異常,不能聲明拋出
 1 class Father{  2     public void show01() throws NullPointerException,ClassCastException{}  3     public void show02() throws IndexOutOfBoundsException{}  4     public void show03() throws IndexOutOfBoundsException{}  5     public void show04() {}  6 }  7 
 8 class Son extends Father{  9     //子類重寫父類方法時,拋出和父類相同的異常
10     public void show01() throws NullPointerException,ClassCastException{} 11     //子類重寫父類方法時,拋出父類異常的子類
12     public void show02() throws IndexOutOfBoundsException{} 13     //子類重寫父類方法時,不拋出異常
14     public void show03() throws IndexOutOfBoundsException{} 15     //子類重寫父類方法時,父類沒有拋出異常,子類本身處理異常
16     public void show04() { 17         try { 18             throw new Exception("出現異常"); 19         } catch (Exception e) { 20  e.printStackTrace(); 21  } 22  } 23 }

2.自定義異常類

  • 繼承Exception處理方式:
    • 第一種:拋出異常給調用者,須要聲明
    • 第二種:本身處理
  • 繼承RuntimeException處理方式:
    • 直接拋出,不用聲明直接交給JVM處理
 1 public class demo02 {  2 
 3 
 4     //拋出異常給調用者處理,須要聲明
 5     public static void testException1() throws TestException {  6         System.out.println("這是testException1");  7         throw new TestException("testException1");  8  }  9 
10 
11     //拋出異常本身處理
12     public static void testException2() { 13         System.out.println("這是testException2"); 14         try { 15             throw new TestException("testException1"); 16         } catch (TestException e) { 17  e.printStackTrace(); 18             return ;    //用於結束方法
19  } 20 
21  } 22 
23     //運行時異常不用處理和聲明,交給JVM處理,最終中斷處理
24     public static void testRuntimeException(){ 25         System.out.println("這是testRuntimeException"); 26         throw new TestRuntimeException("testRuntimeException"); 27  } 28 
29     public static void main(String[] args) throws TestException { 30 
31  demo02.testException1(); 32  demo02.testException2(); 33  demo02.testRuntimeException(); 34      
35  } 36 }

TestException

 1 public class TestException extends Exception {  2 
 3     public TestException() {  4         super();  5  }  6 
 7     public TestException(String message) {  8         super(message);  9  } 10 }

TestRuntimeException

 1 public class TestRuntimeException extends RuntimeException{  2 
 3     public TestRuntimeException() {  4         super();  5  }  6 
 7     public TestRuntimeException(String message) {  8         super(message);  9  } 10 }

補充:Java常見異常

1. RuntimeException子類:

序號 異常名稱 異常描述
1 java.lang.ArrayIndexOutOfBoundsException 數組索引越界異常。當對數組的索引值爲負數或大於等於數組大小時拋出。
2 java.lang.ArithmeticException  算術條件異常。譬如:整數除零等。
3 java.lang.SecurityException  安全性異常
4 java.lang.IllegalArgumentException 非法參數異常
5 java.lang.ArrayStoreException  數組中包含不兼容的值拋出的異常 
6 java.lang.NegativeArraySizeException 數組長度爲負異常 
7 java.lang.NullPointerException 空指針異常。當應用試圖在要求使用對象的地方使用了null時,拋出該異常。譬如:調用null對象的實例方法、訪問null對象的屬性、計算null對象的長度、使用throw語句拋出null等等。

 2.IOException

序號 異常名稱 異常描述
1 IOException 操做輸入流和輸出流時可能出現的異常
2 EOFException 文件已結束異常
3 FileNotFoundException 文件未找到異常

 3. 其餘

序號 異常名稱 異常描述
1 ClassCastException 類型轉換異常類
2 ArrayStoreException 數組中包含不兼容的值拋出的異常
3 SQLException 操做數據庫異常類
4 NoSuchFieldException 字段未找到異常
5 NoSuchMethodException 方法未找到拋出的異常
6 NumberFormatException 字符串轉換爲數字拋出的異常
7 StringIndexOutOfBoundsException 字符串索引超出範圍拋出的異常
8 IllegalAccessException 不容許訪問某類異常
9 InstantiationException

 當應用程序試圖使用Class類中的newInstance()方法建立

一個類的實例,而指定的類對象沒法被實例化時,拋出該異常

10 java.lang.ClassNotFoundException 找不到類異常。當應用試圖根據字符串形式的類名構造類,而在遍歷CLASSPAH以後找不到對應名稱的class文件時,拋出該異常。


3.多線程

(1)Thread類:

構造方法:

  • public Thread():分配一個新的線程對象
  • public Thread(String name):分配一個指定名字的新的線程對象
  • public Thread(Runnable target):分配一個帶有指定目標新的線程對象
  • public Thread(Runnable target,String name):分配一個帶有指定目標新的線程對象並指定名字

經常使用方法:

  • public String getName():獲取當前線程名稱
  • public void setName():設置當前線程名稱
  • public void start():讓線程開始執行,Java虛擬機調用該線程的run方法
  • public void run():該線程要執行的任務在此處定義代碼
  • public static void sleep(long millis):是當前正在執行的線程以指定的毫秒數暫停
  • public static Thread currentThread():返回對當前正在執行的線程對象的引用

MyThread類:

 1 public class MyThread extends Thread {  2 
 3     public MyThread() {  4  }  5 
 6     public MyThread(String name) {  7         super(name);  8  }  9 
10  @Override 11     public void run() { 12 
13  System.out.println(Thread.currentThread().getName()); 14  } 15 }

demo01類:

 1 public class demo01 {  2 
 3 
 4     public static void main(String[] args) {  5 
 6         //1.修改線程名稱方法一
 7         MyThread thread1 = new MyThread();  8         thread1.setName("thread1");  9         thread1.start();  //thread1 10 
11         //修改線程名稱方法二
12         MyThread thread2 = new MyThread("thread2"); 13         thread2.start();    //thread2
14 
15 
16         for (int i = 0; i < 60; i++) { 17  System.out.println(i); 18 
19             //2.使當前線程睡眠1秒執行一次
20             try { 21                 Thread.sleep(1000); 22             } catch (InterruptedException e) { 23  e.printStackTrace(); 24  } 25  } 26 
27 
28  } 29 }

(2)Runnable接口:

實現步驟:

  1. 建立一個Runnable接口的實現類
  2. 在實現類中重寫Runnable接口的run方法,設置線程任務
  3. 建立一個Runnable接口的實現類對象
  4. 建立Thread類對象,構造方法中傳遞Runnable接口實現類對象
  5. 調用start方法,開啓新線程執行run方法

RunnableImpl類:

1 public class RunnableImpl implements Runnable { 2  @Override 3     public void run() { 4 
5         for (int i = 0; i < 60; i++) { 6             System.out.println(Thread.currentThread().getName() + ":" + i); 7  } 8  } 9 }

demo02類:

 1 public class demo02 {  2 
 3     public static void main(String[] args) {  4 
 5         RunnableImpl runnable = new RunnableImpl();  6         new Thread(runnable).start();  7 
 8         for (int i = 0; i < 60; i++) {  9 
10             System.out.println(Thread.currentThread().getName() + ":" + i); 11 
12  } 13  } 14 }

(3)實現Runnable接口和繼承Thread類比較

實現Runnable接口優勢:

  • 避免了單繼承的侷限性
  • 加強了程序的擴展性,下降了程序的耦合性:把設置線程任務和開啓線程分離

(4)匿名內部類實現線程的建立

優勢:簡化代碼

 1 public class demo03 {  2 
 3     public static void main(String[] args) {  4 
 5         //匿名Thread
 6         new Thread() {  7  @Override  8             public void run() {  9 
10                 for (int i = 0; i < 20; i++) { 11                     System.out.println("Thread:" + i); 12  } 13  } 14  }.start(); 15 
16         //匿名Runnable
17         new Thread(new Runnable() { 18  @Override 19             public void run() { 20                 for (int i = 0; i < 20; i++) { 21                     System.out.println("Runnable:" + i); 22  } 23  } 24  }).start(); 25  } 26 }

4.併發和並行

併發:多個任務在同一時間段執行,任務交替執行

並行:多個任務在同一時刻執行,任務同時執行

線程:進程中的一個執行單元

進程:內存中運行的一個應用程序

(1)線程安全

1.同步代碼塊:

synchronized (同步鎖){ 須要同步操做的代碼(訪問了共享數據的代碼) }

保證了線程安全,可是頻繁的判斷鎖,釋放鎖和獲取鎖致使程序的效率下降

RunableImpl類:this指RunnableImpl建立的對象

 1 public class RunnableImpl implements Runnable {  2 
 3     private int num = 100;  4 
 5  @Override  6     public void run() {  7 
 8         while (true){  9 
10             synchronized (this){ 11                 if (num > 0){ 12                     try { 13                         Thread.sleep(10); 14                     } catch (InterruptedException e) { 15  e.printStackTrace(); 16  } 17                     System.out.println(Thread.currentThread().getName() + "得到當前數字爲:" + num); 18                     num--; 19  } 20  } 21  } 22 
23  } 24 }

demo04類:

 1 public class demo04 {  2 
 3     public static void main(String[] args) {  4 
 5         RunnableImpl runnable = new RunnableImpl();  6         new Thread(runnable).start();  7         new Thread(runnable).start();  8         new Thread(runnable).start();  9 
10 
11  } 12 }

2.同步方法:

public synchronized void method(){ 可能會產生線程安全的代碼 }

保證了線程安全,可是頻繁的判斷鎖,釋放鎖和獲取鎖致使程序的效率下降

RunnableImpl類:this指RunnableImpl建立的對象

 1 public class RunnableImpl implements Runnable {  2 
 3     private int num = 100;  4 
 5  @Override  6     public void run() {  7 
 8         while (true){  9 
10  fun(); 11  } 12 
13  } 14 
15     public synchronized void fun(){ 16         if (num > 0){ 17             try { 18                 Thread.sleep(10); 19             } catch (InterruptedException e) { 20  e.printStackTrace(); 21  } 22             System.out.println(Thread.currentThread().getName() + "得到當前數字爲:" + num); 23             num--; 24  } 25  } 26 }

demo05:

 1 public class demo05 {  2 
 3     public static void main(String[] args) {  4 
 5         RunnableImpl runnable = new RunnableImpl();  6         new Thread(runnable).start();  7         new Thread(runnable).start();  8         new Thread(runnable).start();  9 
10  } 11 }

3.靜態同步方法:

public static synchronized void method(){ 可能會產生線程安全的代碼,只能調用靜態屬性 }

RunnableImpl:靜態方法的鎖對象是本類的class屬性-->class文件對象

 1 public class RunnableImpl implements Runnable {  2 
 3     private static int num = 100; //靜態屬性
 4 
 5  @Override  6     public void run() {  7 
 8         while (true){  9 
10  fun(); 11  } 12 
13  } 14 
15     //靜態方法
16     public static synchronized void fun(){ 17         if (num > 0){ 18             try { 19                 Thread.sleep(10); 20             } catch (InterruptedException e) { 21  e.printStackTrace(); 22  } 23             System.out.println(Thread.currentThread().getName() + "得到當前數字爲:" + num); 24             num--; 25  } 26  } 27 }

另外一種寫法:

 1     //靜態方法
 2     public static void fun() {  3         synchronized (RunnableImpl.class) {  4             if (num > 0) {  5                 try {  6                     Thread.sleep(10);  7                 } catch (InterruptedException e) {  8  e.printStackTrace();  9  } 10                 System.out.println(Thread.currentThread().getName() + "得到當前數字爲:" + num); 11                 num--; 12  } 13  } 14 
15     }

demo06:

 1 public class demo06 {  2 
 3     public static void main(String[] args) {  4 
 5         RunnableImpl runnable = new RunnableImpl();  6         new Thread(runnable).start();  7         new Thread(runnable).start();  8         new Thread(runnable).start();  9 
10  } 11 }

(2)Lock鎖

 java.util.concurrent.locks.Lock 機制提供了比 synchronized 代碼塊和方法更加普遍的鎖定操做。

Lock鎖也稱爲同步鎖

  • Lock是一個接口在1.5版本後出現
  • public void lock():加同步鎖
  • public void unlock():釋放同步鎖
  • ReentrantLock實現了Lock接口
 1 public class RunnableImpl implements Runnable {  2 
 3     private int num = 100; //靜態屬性
 4 
 5     private final ReentrantLock lock = new ReentrantLock();  6 
 7  @Override  8     public void run() {  9 
10         while (true) { 11 
12             //加鎖
13  lock.lock(); 14 
15             if (num > 0) { 16                 try { 17                     Thread.sleep(10); 18                     System.out.println(Thread.currentThread().getName() + "得到當前數字爲:" + num); 19                     num--; 20                 } catch (InterruptedException e) { 21  e.printStackTrace(); 22                 } finally { 23 
24                     //釋放鎖
25  lock.unlock(); 26  } 27  } 28  } 29  } 30 }

(3)線程的狀態

  • 初始(NEW):新建立了一個線程對象,但尚未調用start()方法。
  • 運行(RUNNABLE):Java線程中將就緒(ready)和運行中(running)兩種狀態籠統的稱爲「運行」。線程對象建立後,其餘線程(好比main線程)調用了該對象的start()方法。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取CPU的使用權,此時處於就緒狀態(ready)。就緒狀態的線程在得到CPU時間片後變爲運行中狀態(running)。
  • 阻塞(BLOCKED):表示線程阻塞於鎖。
  • 等待(WAITING):進入該狀態的線程須要等待其餘線程作出一些特定動做(通知或中斷)。
  • 計時等待(TIMED_WAITING):該狀態不一樣於WAITING,它能夠在指定的時間後自行返回。
  • 死亡(TERMINATED):表示該線程已經執行完畢。

(4)線程經常使用方法:

  • void wait():當前線程進入等待狀態
  • void wait(long timeout):當前線程進入等待狀態後,等待指定時間後自動喚醒
  • void notify():喚醒等待的單個線程,隨機喚醒一個
  • void notifyAll():喚醒等待的全部線程

(5)線程通訊案例:單生產者和單消費者問題

產品類:

 1 public class Product {  2 
 3     private String name;  4     private Deque<String> deque;  5 
 6 
 7     public Product(String name, Deque<String> deque) {  8         this.name = name;  9         this.deque = deque; 10  } 11 
12     public Deque<String> getDeque() { 13         return deque; 14  } 15 
16     public void setDeque(Deque<String> deque) { 17         this.deque = deque; 18  } 19 
20     public String getName() { 21         return name; 22  } 23 
24     public void setName(String name) { 25         this.name = name; 26  } 27 
28 
29  @Override 30     public String toString() { 31         return "Product{" +
32                 "name='" + name + '\'' +
33                 ", deque=" + deque +
34                 '}'; 35  } 36 }

單生產者Producer類:

 1 public class Producer implements Runnable {  2 
 3     private Product product;  4     private int productID = 1;  5 
 6     public Producer(Product product, int productID) {  7         this.product = product;  8         this.productID = productID;  9  } 10 
11     public Producer(Product product) { 12         this.product = product; 13  } 14 
15  @Override 16     public void run() { 17 
18         while (true) { 19             synchronized (product) { 20 
21                 //當前產品數量
22                 int num = product.getDeque().size(); 23 
24                 if (num >= 10) { 25 
26                     try { 27  product.wait(); 28                     } catch (InterruptedException e) { 29  e.printStackTrace(); 30  } 31  } 32 
33                 num = product.getDeque().size(); 34 
35                 String name =  productID + "號產品"; 36  product.getDeque().add(name); 37                 productID++; 38 
39                 System.out.println("+++++當前庫存" + num + "件產品,正在生成產品名爲" + name + "的產品"); 40 
41 
42                 try { 43                     Thread.sleep(100); 44                 } catch (InterruptedException e) { 45  e.printStackTrace(); 46  } 47 
48 
49 
50                 System.out.println("++++++++++++++++已經生產好了產品名爲" + name + "的產品,當前庫存" + (num + 1) + "件產品"); 51  product.notify(); 52 
53  } 54  } 55  } 56 }

單消費者Consumer類:

 1 public class Consumer implements Runnable {  2 
 3     private String name;  4     private Product product;  5 
 6     public Consumer(String name, Product product) {  7         this.name = name;  8         this.product = product;  9  } 10 
11  @Override 12     public void run() { 13 
14         while (true) { 15 
16             synchronized (product) { 17 
18 
19                 int num = product.getDeque().size(); 20 
21                 if (num <= 0) { 22                     try { 23  product.wait(); 24                     } catch (InterruptedException e) { 25  e.printStackTrace(); 26  } 27  } 28 
29                 num = product.getDeque().size(); 30 
31                 String name = product.getDeque().getFirst(); 32 
33                 System.out.println("-----消費者" + this.name + "正在消費產品名爲" + name + "的產品"); 34 
35  product.getDeque().remove(); 36 
37                 try { 38                     Thread.sleep(50); 39                 } catch (InterruptedException e) { 40  e.printStackTrace(); 41  } 42 
43                 System.out.println("--------------消費者" + this.name + "已經消費了產品名爲" + name + "的產品,剩餘庫存" + (num - 1)); 44  product.notify(); 45 
46 
47  } 48  } 49  } 50 }

測試代碼:

 1 public class day01 {  2 
 3     public static void main(String[] args) {  4 
 5         Deque<String> deque = new ArrayDeque<>();  6         Product product = new Product("產品類別1",deque);  7 
 8         new Thread(new Producer(product)).start();  9         new Thread(new Consumer("Consumer1", product)).start(); 10 
11 
12  } 13 }

5.線程池

  • JDK1.5以後提供的線程池
  • 頂層接口java.util.concurrent.Executor做爲執行線程的工具而不是真正的線程池。
  • java.util.concurrent.ExecutorService爲線程池接口繼承Executor接口。
  • java.util.concurrent.Executors爲線程池的工廠類。

(1)線程池經常使用方法

  • Executors工廠類中:
    • public static ExecutorService new FixedThreadPool(int nThreads):建立一個能夠重用的固定線程數的線程池
  • ExecutorService線程池接口:
    • Future<?> submit(Runnable task):提交一個Runnable任務執行。

    • void shutdown():銷燬線程池。

(2)執行步驟:

  1. 使用Executors工廠類調用FixedThreadPool方法來建立一個線程池。
  2. 編寫一個實現Runnable接口的實現類,重寫run方法。
  3. 調用ExecutorService類方法submit傳入Runnable的實現類並執行。
  4. 最終銷燬線程池ExecutorService類方法shutdown,現實中不必定須要。

 RunnableImpl:

 1 public class RunnableImpl implements Runnable {  2 
 3     private int num = 2;  4  @Override  5     public void run() {  6         while (num-- > 0) {  7 
 8  System.out.println(Thread.currentThread().getName());  9             try { 10                 Thread.sleep(100); 11             } catch (InterruptedException e) { 12  e.printStackTrace(); 13  } 14  } 15  } 16 }

main:

 1 public class demo01 {  2 
 3     public static void main(String[] args) {  4 
 5         //建立
 6         ExecutorService es = Executors.newFixedThreadPool(10);  7 
 8         //執行
 9         es.submit(new RunnableImpl());  //pool-1-thread-1
10         es.submit(new RunnableImpl());  //pool-1-thread-2
11         es.submit(new RunnableImpl());  //pool-1-thread-3
12         es.submit(new RunnableImpl());  //pool-1-thread-4 13 
14         //銷燬
15  es.shutdown(); 16 
17  } 18 }
相關文章
相關標籤/搜索