線程簡介 什麼是線程 現代操作系統調度的最小單元是線程,也叫輕量級進程(Light Weight Process),在一個進程里可以創建多個線程,這些線程都擁有各自的計數器、堆棧和局部變數等屬性,並且能夠訪問共用的記憶體變數。 線程生命周期 java.lang.Thread.State 中定義了 6 ...
線程簡介
什麼是線程
現代操作系統調度的最小單元是線程,也叫輕量級進程(Light Weight Process),在一個進程里可以創建多個線程,這些線程都擁有各自的計數器、堆棧和局部變數等屬性,並且能夠訪問共用的記憶體變數。
線程生命周期
java.lang.Thread.State
中定義了 6 種不同的線程狀態,在給定的一個時刻,線程只能處於其中的一個狀態。
以下是各狀態的說明,以及狀態間的聯繫:
- 開始(New) - 還沒有調用
start()
方法的線程處於此狀態。 - 可運行(Runnable) - 已經調用了
start()
方法的線程狀態。此狀態意味著,線程已經準備好了,一旦被線程調度器分配了 CPU 時間片,就可以運行線程。 - 阻塞(Blocked) - 阻塞狀態。線程阻塞的線程狀態等待監視器鎖定。處於阻塞狀態的線程正在等待監視器鎖定,以便在調用
Object.wait()
之後輸入同步塊/方法或重新輸入同步塊/方法。 - 等待(Waiting) - 等待狀態。一個線程處於等待狀態,是由於執行了 3 個方法中的任意方法:
Object.wait()
Thread.join()
LockSupport.park()
- 定時等待(Timed waiting) - 等待指定時間的狀態。一個線程處於定時等待狀態,是由於執行了以下方法中的任意方法:終止(Terminated) - 線程
run()
方法執行結束,或者因異常退出了run()
方法,則該線程結束生命周期。死亡的線程不可再次復生。Thread.sleep(sleeptime)
Object.wait(timeout)
Thread.join(timeout)
LockSupport.parkNanos(timeout)
LockSupport.parkUntil(timeout)
啟動和終止線程
構造線程
構造線程主要有三種方式
- 繼承
Thread
類 - 實現
Runnable
介面 - 實現
Callable
介面
繼承 Thread 類
通過繼承 Thread 類構造線程的步驟:
- 定義 Thread 類的子類,並重寫該類的 run() 方法,該 run() 方法的方法體就代表了線程要完成的任務。因此把 run() 方法稱為執行體。
- 創建 Thread 子類的實例,即創建了線程對象。
- 調用線程對象的 start() 方法來啟動該線程。
示例:
public class ThreadDemo02 { public static void main(String[] args) { Thread02 mt1 = new Thread02("線程A "); // 實例化對象 Thread02 mt2 = new Thread02("線程B "); // 實例化對象 mt1.start(); // 調用線程主體 mt2.start(); // 調用線程主體 } static class Thread02 extends Thread { private int ticket = 5; Thread02(String name) { super(name); } @Override public void run() { for (int i = 0; i < 100; i++) { if (this.ticket > 0) { System.out.println(this.getName() + " 賣票:ticket = " + ticket--); } } } } }
實現 Runnable 介面
通過實現 Runnable 介面構造線程的步驟:
- 定義 Runnable 介面的實現類,並重寫該介面的 run() 方法,該 run() 方法的方法體同樣是該線程的線程執行體。
- 創建 Runnable 實現類的實例,並依此實例作為 Thread 的 target 來創建 Thread 對象,該 Thread 對象才是真正的線程對象。
- 調用線程對象的 start() 方法來啟動該線程。
示例:
public class RunnableDemo { public static void main(String[] args) { MyThread t = new MyThread("Runnable 線程"); // 實例化對象 new Thread(t).run(); // 調用線程主體 new Thread(t).run(); // 調用線程主體 new Thread(t).run(); // 調用線程主體 } static class MyThread implements Runnable { private int ticket = 5; private String name; MyThread(String name) { this.name = name; } @Override public void run() { for (int i = 0; i < 100; i++) { if (this.ticket > 0) { System.out.println(this.name + " 賣票:ticket = " + ticket--); } } } } }
實現 Callable 介面
通過實現 Callable 介面構造線程的步驟:
- 創建 Callable 介面的實現類,並實現 call() 方法,該 call() 方法將作為線程執行體,並且有返回值。
- 創建 Callable 實現類的實例,使用 FutureTask 類來包裝 Callable 對象,該 FutureTask 對象封裝了該 Callable 對象的 call() 方法的返回值。
- 使用 FutureTask 對象作為 Thread 對象的 target 創建並啟動新線程。
- 調用 FutureTask 對象的 get() 方法來獲得子線程執行結束後的返回值。
示例:
public class CallableAndFutureDemo { public static void main(String[] args) { Callable<Integer> callable = () -> new Random().nextInt(100); FutureTask<Integer> future = new FutureTask<>(callable); new Thread(future).start(); try { Thread.sleep(1000);// 可能做一些事情 System.out.println(future.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } }
三種創建線程方式對比
- 實現 Runnable 介面優於繼承 Thread 類,因為實現介面方式更便於擴展類。
- 實現 Runnable 介面的線程沒有返回值;而實現 Callable 介面的線程有返回值。
中斷線程
當一個線程運行時,另一個線程可以直接通過 interrupt()
方法中斷其運行狀態。
public class ThreadInterruptDemo { public static void main(String[] args) { MyThread mt = new MyThread(); // 實例化Runnable子類對象 Thread t = new Thread(mt, "線程"); // 實例化Thread對象 t.start(); // 啟動線程 try { Thread.sleep(2000); // 線程休眠2秒 } catch (InterruptedException e) { System.out.println("3、休眠被終止"); } t.interrupt(); // 中斷線程執行 } static class MyThread implements Runnable { @Override public void run() { System.out.println("1、進入run()方法"); try { Thread.sleep(10000); // 線程休眠10秒 System.out.println("2、已經完成了休眠"); } catch (InterruptedException e) { System.out.println("3、休眠被終止"); return; // 返回調用處 } System.out.println("4、run()方法正常結束"); } } }
終止線程
Thread 中的 stop 方法有缺陷,已廢棄。
安全地終止線程有兩種方法:
- 中斷狀態是線程的一個標識位,而中斷操作是一種簡便的線程間交互方式,而這種交互方式最適合用來取消或停止任務。
- 還可以利用一個 boolean 變數來控制是否需要停止任務並終止該線程。
public class ThreadStopDemo03 { public static void main(String[] args) throws Exception { MyTask one = new MyTask(); Thread countThread = new Thread(one, "CountThread"); countThread.start(); // 睡眠1秒,main線程對CountThread進行中斷,使CountThread能夠感知中斷而結束 TimeUnit.SECONDS.sleep(1); countThread.interrupt(); MyTask two = new MyTask(); countThread = new Thread(two, "CountThread"); countThread.start(); // 睡眠1秒,main線程對Runner two進行取消,使CountThread能夠感知on為false而結束 TimeUnit.SECONDS.sleep(1); two.cancel(); } private static class MyTask implements Runnable { private long i; private volatile boolean on = true; @Override public void run() { while (on && !Thread.currentThread().isInterrupted()) { i++; } System.out.println("Count i = " + i); } void cancel() { on = false; } } }
Thread 中的重要方法
run
- 線程的執行實體。start
- 線程的啟動方法。setName
、getName
- 可以通過 setName()、 getName() 來設置、獲取線程名稱。setPriority
、getPriority
- 在 Java 中,所有線程在運行前都會保持在就緒狀態,那麼此時,哪個線程優先順序高,哪個線程就有可能被先執行。可以通過 setPriority、getPriority 來設置、獲取線程優先順序。setDaemon
、isDaemon
- 可以使用 setDaemon() 方法設置線程為守護線程;可以使用 isDaemon() 方法判斷線程是否為守護線程。isAlive
- 可以通過 isAlive 來判斷線程是否啟動。interrupt
- 當一個線程運行時,另一個線程可以直接通過 interrupt() 方法中斷其運行狀態。join
- 使用 join() 方法讓一個線程強制運行,線程強制運行期間,其他線程無法運行,必須等待此線程完成之後才可以繼續執行。Thread.sleep
- 使用 Thread.sleep() 方法即可實現休眠。Thread.yield
- 可以使用 Thread.yield() 方法將一個線程的操作暫時讓給其他線程執行。
設置/獲取線程名稱
在 Thread 類中可以通過 setName()
、 getName()
來設置、獲取線程名稱。
public class ThreadNameDemo { public static void main(String[] args) { MyThread mt = new MyThread(); // 實例化Runnable子類對象 new Thread(mt).start(); // 系統自動設置線程名稱 new Thread(mt, "線程-A").start(); // 手工設置線程名稱 Thread t = new Thread(mt); // 手工設置線程名稱 t.setName("線程-B"); t.start(); } static class MyThread implements Runnable { @Override public void run() { for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName() + "運行,i = " + i); // 取得當前線程的名字 } } } }
判斷線程是否啟動
在 Thread 類中可以通過 isAlive()
來判斷線程是否啟動。
public class ThreadAliveDemo { public static void main(String[] args) { MyThread mt = new MyThread(); // 實例化Runnable子類對象 Thread t = new Thread(mt, "線程"); // 實例化Thread對象 System.out.println("線程開始執行之前 --> " + t.isAlive()); // 判斷是否啟動 t.start(); // 啟動線程 System.out.println("線程開始執行之後 --> " + t.isAlive()); // 判斷是否啟動 for (int i = 0; i < 3; i++) { System.out.println(" main運行 --> " + i); } // 以下的輸出結果不確定 System.out.println("代碼執行之後 --> " + t.isAlive()); // 判斷是否啟動 } static class MyThread implements Runnable { @Override public void run() { for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName() + "運行,i = " + i); } } } }
守護線程
在 Java 程式中,只要前臺有一個線程在運行,則整個 Java 進程就不會消失,所以此時可以設置一個守護線程,這樣即使 Java 進程結束了,此守護線程依然會繼續執行。可以使用 setDaemon()
方法設置線程為守護線程;可以使用 isDaemon()
方法判斷線程是否為守護線程。
public class ThreadDaemonDemo { public static void main(String[] args) { Thread t = new Thread(new MyThread(), "線程"); t.setDaemon(true); // 此線程在後臺運行 System.out.println("線程 t 是否是守護進程:" + t.isDaemon()); t.start(); // 啟動線程 } static class MyThread implements Runnable { @Override public void run() { while (true) { System.out.println(Thread.currentThread().getName() + "在運行。"); } } } }
設置/獲取線程優先順序
在 Java 中,所有線程在運行前都會保持在就緒狀態,那麼此時,哪個線程優先順序高,哪個線程就有可能被先執行。
public class ThreadPriorityDemo { public static void main(String[] args) { System.out.println("主方法的優先順序:" + Thread.currentThread().getPriority()); System.out.println("MAX_PRIORITY = " + Thread.MAX_PRIORITY); System.out.println("NORM_PRIORITY = " + Thread.NORM_PRIORITY); System.out.println("MIN_PRIORITY = " + Thread.MIN_PRIORITY); Thread t1 = new Thread(new MyThread(), "線程A"); // 實例化線程對象 Thread t2 = new Thread(new MyThread(), "線程B"); // 實例化線程對象 Thread t3 = new Thread(new MyThread(), "線程C"); // 實例化線程對象 t1.setPriority(Thread.MIN_PRIORITY); // 優先順序最低 t2.setPriority(Thread.MAX_PRIORITY); // 優先順序最低 t3.setPriority(Thread.NORM_PRIORITY); // 優先順序最低 t1.start(); // 啟動線程 t2.start(); // 啟動線程 t3.start(); // 啟動線程 }
static class MyThread implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { try { Thread.sleep(500); // 線程休眠 } catch (InterruptedException e) { e.printStackTrace(); } // 取得當前線程的名字 String out = Thread.currentThread().getName() + ",優先順序:" + Thread.currentThread().getPriority() + ",運行:i = " + i; System.out.println(out); } } } }
線程間通信
wait/notify/notifyAll
wait、notify、notifyAll 是 Object 類中的方法。
wait
- 線程自動釋放其占有的對象鎖,並等待 notify。notify
- 喚醒一個正在 wait 當前對象鎖的線程,並讓它拿到對象鎖。notifyAll
- 喚醒所有正在 wait 前對象鎖的線程。
生產者、消費者示例:
public class ThreadWaitNotifyDemo02 { private static final int QUEUE_SIZE = 10; private static final PriorityQueue<Integer> queue = new PriorityQueue<>(QUEUE_SIZE); public static void main(String[] args) { new Producer("生產者A").start(); new Producer("生產者B").start(); new Consumer("消費者A").start(); new Consumer("消費者B").start(); } static class Consumer extends Thread { Consumer(String name) { super(name); } @Override public void run() { while (true) { synchronized (queue) { while (queue.size() == 0) { try { System.out.println("隊列空,等待數據"); queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); queue.notifyAll(); } } queue.poll(); // 每次移走隊首元素 queue.notifyAll(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 從隊列取走一個元素,隊列當前有:" + queue.size() + "個元素"); } } } } static class Producer extends Thread { Producer(String name) { super(name); } @Override public void run() { while (true) { synchronized (queue) { while (queue.size() == QUEUE_SIZE) { try { System.out.println("隊列滿,等待有空餘空間"); queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); queue.notifyAll(); } } queue.offer(1); // 每次插入一個元素 queue.notifyAll(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 向隊列取中插入一個元素,隊列當前有:" + queue.size() + "個元素"); } } } } }
線程的禮讓
線上程操作中,可以使用 Thread.yield()
方法將一個線程的操作暫時讓給其他線程執行。
public class ThreadYieldDemo { public static void main(String[] args) { MyThread t = new MyThread(); new Thread(t, "線程A").start(); new Thread(t, "線程B").start(); } static class MyThread implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "運行,i = " + i); if (i == 2) { System.out.print("線程禮讓:"); Thread.yield(); } } } } }
線程的強制執行
線上程操作中,可以使用 join()
方法讓一個線程強制運行,線程強制運行期間,其他線程無法運行,必須等待此線程完成之後才可以繼續執行。
public class ThreadJoinDemo { public static void main(String[] args) { MyThread mt = new MyThread(); // 實例化Runnable子類對象 Thread t = new Thread(mt, "mythread"); // 實例化Thread對象 t.start(); // 啟動線程 for (int i = 0; i < 50; i++) { if (i > 10) { try { t.join(); // 線程強制運行 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Main 線程運行 --> " + i); } }
static class MyThread implements Runnable { @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println(Thread.currentThread().getName() + " 運行,i = " + i); // 取得當前線程的名字 } } } }
線程的休眠
直接使用 Thread.sleep()
方法即可實現休眠。
public class ThreadSleepDemo { public static void main(String[] args) { new Thread(new MyThread("線程A", 1000)).start(); new Thread(new MyThread("線程A", 2000)).start(); new Thread(new MyThread("線程A", 3000)).start(); } static class MyThread implements Runnable { private String name; private int time; private MyThread(String name, int time) { this.name = name; // 設置線程名稱 this.time = time; // 設置休眠時間 } @Override public void run() { try { Thread.sleep(this.time); // 休眠指定的時間 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(this.name + "線程,休眠" + this.time + "毫秒。"); } } }
ThreadLocal
ThreadLocal,很多地方叫做線程本地變數,也有些地方叫做線程本地存儲,其實意思差不多。可能很多朋友都知道 ThreadLocal 為變數在每個線程中都創建了一個副本,那麼每個線程可以訪問自己內部的副本變數。
源碼
ThreadLocal 的主要方法: public class ThreadLocal<T> { public T get() {} public void remove() {} public void set(T value) {} public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {} }
- get()方法是用來獲取 ThreadLocal 在當前線程中保存的變數副本。
- set()用來設置當前線程中變數的副本。
- remove()用來移除當前線程中變數的副本。
- initialValue()是一個 protected 方法,一般是用來在使用時進行重寫的,它是一個延遲載入方法,下麵會詳細說明。
get() 源碼實現
get 源碼 public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
- 取得當前線程。
- 通過 getMap() 方法獲取 ThreadLocalMap。
- 成功,返回 value;失敗,返回 setInitialValue()。
ThreadLocalMap 源碼實現
ThreadLocalMap 源碼
ThreadLocalMap 是 ThreadLocal 的一個內部類。
ThreadLocalMap 的 Entry 繼承了 WeakReference,並且使用 ThreadLocal 作為鍵值。
setInitialValue 源碼實現
private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
如果 map 不為空,就設置鍵值對;為空,再創建 Map,看一下 createMap 的實現:
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
ThreadLocal 源碼小結
至此,可能大部分朋友已經明白了 ThreadLocal 是如何為每個線程創建變數的副本的:
- 在每個線程 Thread 內部有一個 ThreadLocal.ThreadLocalMap 類型的成員變數 threadLocals,這個 threadLocals 就是用來存儲實際的變數副本的,鍵值為當前 ThreadLocal 變數,value 為變數副本(即 T 類型的變數)。
- 在 Thread 裡面,threadLocals 為空,當通過 ThreadLocal 變數調用 get()方法或者 set()方法,就會對 Thread 類中的 threadLocals 進行初始化,並且以當前 ThreadLocal 變數為鍵值,以 ThreadLocal 要保存的副本變數為 value,存到 threadLocals。
- 在當前線程裡面,如果要使用副本變數,就可以通過 get 方法在 threadLocals 裡面查找。
示例
ThreadLocal 最常見的應用場景為用於解決資料庫連接、Session 管理等問題。
示例 - 資料庫連接
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() { public Connection initialValue() { return DriverManager.getConnection(DB_URL); } }; public static Connection getConnection() { return connectionHolder.get(); }
示例 - Session 管理
private static final ThreadLocal threadSession = new ThreadLocal(); public static Session getSession() throws InfrastructureException { Session s = (Session) threadSession.get(); try { if (s == null) { s = getSessionFactory().openSession(); threadSession.set(s); } } catch (HibernateException ex) { throw new InfrastructureException(ex); } return s; }