1. 程式人生 > >Java併發程式設計系列(一)-執行緒的基本使用

Java併發程式設計系列(一)-執行緒的基本使用

最近在學習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毫秒.(可自行多次嘗試).

相關推薦

no