1. 程式人生 > >Java並發編程系列(一)-線程的基本使用

Java並發編程系列(一)-線程的基本使用

col ignore rac param rup java並發編程 lse fin main方法

最近在學習java並發編程基礎.一切從簡,以能理解概念為主.

並發編程肯定繞不過線程.這是最基礎的.

那麽就從在java中,如何使用線程開始.

繼承Thread類

繼承Thread類,重寫run方法,new出對象,調用start方法.

在新啟的線程裏運行的就是重寫的run方法.

 1 /**
 2  * 集成Thread類 實現run()
 3  */
 4 public class C1 extends Thread {
 5 
 6     @Override
 7     public void run() {
 8         try {
 9             Thread.sleep(100);
10 } catch (InterruptedException e) { 11 e.printStackTrace(); 12 } 13 System.out.println(Thread.currentThread().getName() + " run()"); 14 } 15 16 public static void main(String[] args) { 17 C1 c1 = new C1(); 18 c1.start(); 19 } 20 }

run方法裏先睡100毫秒,然後打印當前線程名稱+run()

運行結果:

技術分享圖片

實現Runnable接口

實現Runnable接口run方法
 1 /**
 2  * 實現Runnable接口run()
 3  */
 4 public class C2 implements Runnable {
 5 
 6     @Override
 7     public void run() {
 8         try {
 9             Thread.sleep(100);
10         } catch (InterruptedException e) {
11             e.printStackTrace();
12         }
13 System.out.println(Thread.currentThread().getName() + " run()"); 14 } 15 16 public static void main(String[] args) { 17 C2 c2 = new C2(); 18 new Thread(c2, "thread0").start(); 19 } 20 }

運行結果:

技術分享圖片

Lambda表達式

Lambda表達式本質上還是實現了Runnable的run方法,但是寫起來相當的方便.註:java8或以上版本才支持

java中Lambda表達式的語法:

1.(parameters) -> expression

2.(parameters) ->{ statements; }

3.對象名::方法名 註:方法名後不加小括號

 1 /**
 2  * Lambda表達式 java8或以上版本
 3  */
 4 public class C3 {
 5 
 6     public void foo() {
 7         try {
 8             Thread.sleep(100);
 9         } catch (InterruptedException e) {
10             e.printStackTrace();
11         }
12         System.out.println(Thread.currentThread().getName() + " foo()");
13     }
14 
15     public static void main(String[] args) {
16         C3 c3 = new C3();
17         new Thread(() -> c3.foo()).start();
18         new Thread(() -> {
19             c3.foo();
20         }).start();
21         new Thread(c3::foo).start();
22     }
23 }

運行結果:

技術分享圖片

可以看到三種寫法是等價的.執行無參數的方法,用第三種雙冒號的方式最簡單,有參數的可以用第一種方式,執行多行的代碼片段只能用第二種方式.

start() run() join()

首先看一段代碼吧

 1 /**
 2  * start() run() join()
 3  */
 4 public class C4 {
 5 
 6     public void foo() {
 7         try {
 8             Thread.sleep(100);
 9         } catch (InterruptedException e) {
10             e.printStackTrace();
11         }
12         System.out.println(Thread.currentThread().getName() + " foo()");
13     }
14 
15     public static void main(String[] args) {
16         C4 c4 = new C4();
17         //一個線程不能同時執行多次start() 否側會拋出IllegalThreadStateException
18         Thread tStart=new Thread(c4::foo);
19         tStart.start();
20         tStart.start();
21         //同時執行多次start()沒拋出IllegalThreadStateException 因為不在同一線程內 是new出來的
22         new Thread(c4::foo).start();
23         new Thread(c4::foo).start();
24 
25         try {
26             Thread.sleep(1000);
27         } catch (InterruptedException e) {
28             e.printStackTrace();
29         }
30         System.out.println("---分割線---");
31         //run() 調用的就是重寫的那個run 所以沒有開啟線程 是在主線程裏執行的
32         new Thread(c4::foo).run();
33 
34         System.out.println("---分割線---");
35         SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss:SSS");
36         Thread tJoin = new Thread(c4::foo);
37         tJoin.start();
38         System.out.println(simpleDateFormat.format(new Date()));
39         try {
40             //阻塞代碼 等待線程執行完
41             tJoin.join();
42         } catch (InterruptedException e) {
43             e.printStackTrace();
44         }
45         System.out.println(simpleDateFormat.format(new Date()));
46     }
47 }
start()

先看main方法裏的前5行,在同一時間兩次調用同一個線程的start().

運行結果:

技術分享圖片

拋出了IllegalThreadStateException非法的線程狀態異常.也就是說在同一線程裏,不能同時多次執行同一段代碼.這很好理解了,同時多次執行同一段代碼應該用多個線程.

註釋掉tStart相關代碼,繼續運行

運行結果:

技術分享圖片

第一部分結果說明了同時多次執行同一段代碼應該用多個線程.

start方法開啟新線程,見源碼(刪掉源註釋):

 1 public synchronized void start() {
 2         //判斷線程狀態 拋出IllegalThreadStateException
 3         if (threadStatus != 0)
 4             throw new IllegalThreadStateException();
 5 
 6         group.add(this);
 7 
 8         boolean started = false;
 9         try {
10             //這裏開啟
11             start0();
12             started = true;
13         } finally {
14             try {
15                 if (!started) {
16                     group.threadStartFailed(this);
17                 }
18             } catch (Throwable ignore) {
19             }
20         }
21     }

可以看到調用start首先會判斷線程狀態,不是0的話,拋出非法的線程狀態異常.

真正開啟線程是在start0()這裏.

轉到定義技術分享圖片可以看到start調用了本地方法,也就是真正開啟線程是c語言寫的.那麽什麽參數都沒有.底層這麽知道開啟線程調用什麽方法呢.

最上面,Thread類有個靜態構造函數技術分享圖片是類在實例化的時候,調用registerNatives本地方法,將自己註冊進去的.

run()

第二部分是調用的run方法,顯示的線程名是主線程.就是說調用run方法並不會開啟新線程.

看看到底是怎麽回事.

導航到源碼可以看到,Thread類是實現了Runnable接口的.重寫了run方法.

技術分享圖片技術分享圖片

如果用繼承Thread類的方式寫線程類,要重寫run方法.先不管源碼重寫的run是什麽,那麽你的重寫會覆蓋掉源碼重寫的run.這種情況下,new出自己的線程類,然後調用run,當然和線程沒有任何關系,就是在主線程裏調用了另一個類的普通的方法.

如果用實現Runnable接口的方式寫線程類,那麽會new一個這個接口的實例,傳到new出的Thread對象裏.繼續往下傳到init方法,最終賦值到target變量上

技術分享圖片技術分享圖片技術分享圖片

結合上面源碼重寫的run,那麽它還是調用了你傳過來的Runnable實例的run方法.

join()

join就是阻塞代碼,等待線程執行完後再開繼續始執行當前代碼

可以看到第三部分結果,第一次輸出時間和第二次輸出時間相差了109毫秒(線程內睡了100毫秒).如果不用join阻塞,時間一般相差的會很小,當然具體是多少也不一定,得看機器當時的運行情況.用join相差多少毫秒也不一定,但至少一定不會小於100毫秒.(可自行多次嘗試).

Java並發編程系列(一)-線程的基本使用