1. 程式人生 > >Thread API的詳細介紹

Thread API的詳細介紹

正常 class vat 串行 怎麽 需求 arr \n 暗示

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

import static java.util.stream.Collectors.toList;

public class Test {
    public static void
main(String[] args){ // ThradSleep.test(); // ThreadPriority.test(); // ThreadPriority.test(); // ThreadId.test(); // CurrentThread.test(); // ThreadInterrupt.test(); // ThreadIsInterrupted.test(); // ThreadIsInterrupted2.test(); // ThreadInterrupted.test();
// ThreadTest.test(); // ThreadJoin.test(); // FightQueryExample.test(); FlagThreadExit.test();` } } /* 3.1.1 sleep方法介紹 public static void sleep(long millis) throws InterruptedException public static void sleep(long millis, int nanos) throws InterruptedException 3.1.2 使用TimeUnit代替Thread.sleep TimeUnit可以省去換算的步驟,比如線程想休眠3小時24分17秒88毫秒: TimeUnit.HOURS.sleep(3); TimeUnit.MINUTES.sleep(3); TimeUnit.SECONDS.sleep(3); TimeUnit.MILLISECONDS.sleep(3); ——我是真的需要把Java枚舉部分看一下了。。。
*/ class ThradSleep{ public static void test(){ new Thread(()->{ long startTime=System.currentTimeMillis(); sleep(2_000L); long endTime=System.currentTimeMillis(); System.out.println("Total: "+(endTime-startTime)); }).start(); long startTime=System.currentTimeMillis(); sleep(3_000L); long endTime=System.currentTimeMillis(); System.out.println("Main Total: "+(endTime-startTime)); } private static void sleep(long ms){ try{ Thread.sleep(ms); } catch (InterruptedException e) { e.printStackTrace(); } } } /* 3.2.1 yield方法介紹 調用yield方法會使當前線程從RUNNING狀態切換到RUNNABLE狀態,這個方法一般不常用 ——Java8的流和Lam,早看早練習,穩賺不賠!!! 3.2.2 yield和sleep 其本質區別: 1.sleep會導致當前線程暫停指定的之間,沒有CPU時間片的消耗。(這個CPU時間片消耗指的是什麽?) 2.yield只是對CPU調度器的一個暗示,可能導致線程上下文的切換。 3.sleep會使線程短暫block,會在指定的時間內釋放CPU資源 4.yield會使RUNNING狀態的Thread進入RUNABLE狀態 5.sleep幾乎會百分百地完成指定時間的休眠,而yield的提示並不一定有用 6.一個線程內調用了sleep,另一個線程調用了interrupt,這個調用了sleep的線程會捕獲到 中斷信號,而yield不會。 */ class ThreadYield{ public static void test() { IntStream.range(0,2).mapToObj(ThreadYield::create) .forEach(Thread::start); } private static Thread create(int index) { return new Thread(()->{ if (index == 0) { Thread.yield(); } System.out.println(index); }); } } /* 3.3 設置線程優先級 public final void setPriority(int newPriority); :為線程設定優先級 public final int getPriority(); :獲得線程的優先級 3.3.2 線程優先級源碼分析 要點: 1.線程的優先級不能小於1,不能大於10; 2.如果優先級大於group的優先級,則優先級會被設置為group的優先級; */ class ThreadPriority{ public static void test(){ /* 實驗設置優先級 Thread t1 = new Thread(()->{ while(true){ System.out.println("t1"); } }); Thread t2 = new Thread(()->{ while(true){ System.out.println("t1"); } }); t1.setPriority(3); t2.setPriority(10); t1.start(); t2.start(); */ /* 實驗線程優先級和組優先級關系 ThreadGroup group = new ThreadGroup("test"); group.setMaxPriority(7); Thread t = new Thread(group,"test-thread"); t.setPriority(10); System.out.println("t.getPriority():"+t.getPriority()); */ /* 實驗線程默認優先級 */ Thread t1 = new Thread(); System.out.println("t1 priority:"+t1.getPriority()); Thread t2 = new Thread(()->{ Thread t3 = new Thread(); System.out.println("t3 priority:"+t3.getPriority()); }); t2.setPriority(6); t2.start(); System.out.println("t2 priority:"+t2.getPriority()); } } /* 3.4 獲取線程ID public long getId(); */ class ThreadId{ public static void test(){ System.out.println("main id: "+Thread.currentThread().getId()); Thread t = new Thread("test"); System.out.println("t id: "+t.getId()); } } /* 3.5 獲取當前線程 public static Thread currentThread(); */ class CurrentThread{ public static void test(){ Thread t = new Thread() { public void run() { System.out.println(Thread.currentThread() == this); } }; t.start(); String name = Thread.currentThread().getName(); System.out.println("main".equals(name)); } } /* 3.6 設置線程上下文類加載器 public ClassLoader getContextClassLoader() :獲取 public void setContextClassLoader(ClassLoader cl) :設置 */ /* ************************************************************************* ************************************************************************* ************************************************************************* ************************************************************************* */ /* 3.7 線程interrupt public void interrupt() public static boolean interrupted() public boolean isInterrupted() ——標記一下,我對interrupted()方法有點疑惑,這個方法是怎麽辦到把我一個 對象的標識位給復原了的。額,很尷尬,它調用了一個currentThread方法, 然後間接的設置了這個標誌位,也就說明這個方法只能在某個Thread內部調用。 3.7.1 interrupt 如下方法的調用會使得當前線程進入阻塞狀態: 1.Object的wait(),wait(long),wait(long,int) 2.Thread的sleep(long),sleep(long,int) 3.Thread的join,join(long),join(long,int) 4.InterruptibleChannel的io操作(這個很陌生!!!) 5.Selector的wakeup方法(這個很陌生!!!) 6.其他方法 如果另外一個線程調用阻塞線程的interrupt方法,則會打斷這種阻塞,因此這種方法有時 會被稱為可中斷方法。打斷一個線程並不等於該線程的生命周期的結束,僅僅是打斷了當前 線程的阻塞狀態。一旦線程在阻塞情況下被打斷了,都會拋出一個稱為InterruptedException 的異常,這個異常就像一個signal一樣,高訴這個剛剛被中斷的線程,哦,我剛剛是因為 中斷被喚醒的啊~~~ 要點: interrupt()方法到底做了什麽?在一個線程內部存在一個名為interrupt flag的標識 如果一個線程被interrupt,那麽它的flag將會被設置。如果當前線程只在執行可中斷的 方法,且被阻塞時,調用interrupt方法將其中斷,反而會導致flag被清除~~~(什麽亂 七八糟啊啊啊啊) */ class ThreadInterrupt{ public static void test(){ Thread t = new Thread(()->{ try{ TimeUnit.MINUTES.sleep(1); } catch (InterruptedException e) { System.out.println("Oh,i am be interrupted..."); } }); t.start(); try { TimeUnit.MILLISECONDS.sleep(2); t.interrupt(); //去叫醒這個兄弟!!! } catch (InterruptedException e) { e.printStackTrace(); } } } /* 3.7.2 isInterrupted isInterrupted是Thread的一個成員方法,它主要判斷當前線程是否被中斷,該方法僅僅是對 interrupt標識的一個判斷,並不會影響標識發生任何改變。 ——我感覺作者沒有把阻塞和中斷的許多細節給講清楚 */ class ThreadIsInterrupted{ public static void test(){ Thread t = new Thread(){ @Override public void run() { while(true){ //這個地方真的能被中斷麽??? //do nothing } } }; t.start(); try { TimeUnit.MILLISECONDS.sleep(2); System.out.printf("Thread is interrupted? %s",t.isInterrupted()); t.interrupt(); System.out.printf("Thread is interrupted? %s",t.isInterrupted()); } catch (InterruptedException e) { e.printStackTrace(); } } } class ThreadIsInterrupted2{ public static void test(){ Thread t = new Thread(){ @Override public void run() { while(true){ try{ TimeUnit.MINUTES.sleep(1); } catch (InterruptedException e) { System.out.printf("I am be interrupted? %s\n",isInterrupted()); } } } }; t.start(); try { TimeUnit.MILLISECONDS.sleep(2); System.out.printf("Thread is interrupted? %s \n",t.isInterrupted()); t.interrupt(); System.out.printf("Thread is interrupted? %s \n",t.isInterrupted()); } catch (InterruptedException e) { e.printStackTrace(); } } } /* =========================================================================== 實驗總結 =========================================================================== 實驗結果分析: 1.調用interrupt這個方法了,這個方法只是簡簡單單的設置了一個flag 2.調用了isInterrupted這個方法了,也只是簡簡單單的去訪問一下這個flag 3.線程中如果沒有正在阻塞的方法, 你調用了interrupt是沒有任何意義的,你設置的 這個flag根本沒有方法去訪問 4.如果線程正在某個方法上阻塞這,那麽這個線程被分到時間片時,肯定先檢查一下 這個flag的值,如果這個flag的值代表中斷發生了,就讓這個阻塞的方法返回 ,同時將這個flag復原。 =========================================================================== */ /* 3.7.3 interrupted interrupted是一個靜態方法,調用這個方法會擦除線程的interrupt表示,需要註意 的是,如果線程被打斷了,那麽第一次調用interrupted方法會返回true,並且立即擦 除interrupt標識~~~ ——我想知道的是,這個東西有什麽用!!! */ class ThreadInterrupted{ public static void test() { Thread t = new Thread(){ @Override public void run() { while (true) { /* 我有點不懂這個地方???,你調用的是一個 靜態方法,來消除的是一個對象的標識。。。 */ System.out.println(Thread.interrupted()); } } }; t.setDaemon(true); t.start(); try { TimeUnit.MILLISECONDS.sleep(2); t.interrupted(); } catch (InterruptedException e) { e.printStackTrace(); } } } /* 3.7.4 interrupt註意事項 如果一個線程設置了interrupt標識,那麽接下來的可中斷方法會立即中斷~~~ ——不然會造成中斷信號丟失,問題更大 */ class ThreadTest{ public static void test() { System.out.println("Main thread is interrupted:"+Thread.interrupted()); Thread.currentThread().interrupt(); System.out.println("Main thread is interrupted now?"+Thread.currentThread().isInterrupted()); try{ TimeUnit.MINUTES.sleep(1); } catch (InterruptedException e) { System.out.println("I will be interrupted still."); } } } /* 3.8 線程join public final void join() throws InterruptException public final synchronized void join(long millis, int nanos) throws InterruptException public final synchronized void join(long millis) throws InterruptException 3.8.1 線程join方法詳解 有兩種調用join的方法,main線程創建了A線程對象,然後調用A的join方法,這時候main線程會 等待A線程完成,然後在執行。還有一種就是在A線程內部的run方法中,調用了自生的join方法,不 知道在這種情況下會發生什麽事情。 */ class ThreadJoin{ public static void test() { List<Thread> threads = IntStream.range(1,3) .mapToObj(ThreadJoin::create) .collect(toList()); threads.forEach(Thread::start); for (Thread thread : threads) { try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"#"+i); shortSleep(); } } private static Thread create(int seq) { return new Thread(()->{ for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"#"+i); } },String.valueOf(seq)); } private static void shortSleep() { try{ TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } /* 3.8.2 join方法結合實戰 需求: 如果你有一個APP,主要用於查詢航班信息,你的APP是沒有這些實時數據的,當用戶發起查詢 請求時,你需要到各大航空公司的接口獲取信息,最後同意整理加工返回到APP客戶端。當然 JDK自帶了很多高級工具,比如CountDownLatch和CyclieBarrier等都可以完成類似的功能, 該例子是典型的串行任務局部並行化處理~~~用戶在APP客戶端輸入出發地北京和目的地上海後, 服務端接受到這個請求之後,先來驗證用戶的信息,然後到各大航空公司的接口查詢信息,最後 經過整理加工返回給客戶端,每一個航空公司的接口不會都一樣,獲取數據格式也不一樣,查詢 的速度也存在差異,如果再更航空公司進行串行化交互(逐個地查詢),很明顯客戶端需要等待 很長的時間,這樣的話,用戶體驗就會非常差。如果我們將每個航空公司的查詢都交給一個線程 去工作,然後在它們結束之後同意對數據進行整理,這樣就可以極大的節約時間,從而提高用戶 體驗效果。 */ interface FightQuery{ List<String> get(); } class FightQueryTask extends Thread implements FightQuery{ private final String origin; private final String destination; private final List<String> fightList = new ArrayList<>(); public FightQueryTask(String airline,String origin,String destination){ super("["+airline+"]"); this.origin=origin; this.destination=destination; } @Override public void run() { System.out.printf("%s-query from %s to %s \n",getName(),origin,destination); int randomVal = ThreadLocalRandom.current().nextInt(10); try{ TimeUnit.SECONDS.sleep(randomVal); this.fightList.add(getName()+"-"+randomVal); System.out.printf("The Fight:%s list query successful\n",getName()); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public List<String> get() { return this.fightList; } } class FightQueryExample{ private static List<String> fightCompany = Arrays.asList( "CSA","CEA","HNA" ); public static void test() { List<String> results = search("SH","BJ"); System.out.println("=================result================="); results.forEach(System.out::println); } private static List<String> search(String original,String dest){ final List<String> result = new ArrayList<>(); List<FightQueryTask> tasks = fightCompany.stream() .map(f->createSearchTask(f,original,dest)) .collect(toList()); tasks.forEach(Thread::start); tasks.forEach(t->{ try{ t.join(); } catch (InterruptedException e) { e.printStackTrace(); } }); tasks.stream().map(FightQueryTask::get) .forEach(result::addAll); return result; } private static FightQueryTask createSearchTask( String fight, String original, String dest ){ return new FightQueryTask(fight,original,dest); } } /* ************************************************************************* ************************************************************************* ************************************************************************* ************************************************************************* */ /* 3.9 如何關閉一個線程 3.9.1 正常關閉 1.線程結束,生命周期正常結束 2.捕獲中斷信號關閉線程 用過new Thread的方法創建線程,因此在用這種方式創建的一個線程中往往會循環地執行 某個任務,比如心跳檢查,不斷地接受網絡消息報文等,系統決定退出的時候,可以借助中斷 線程的方式使其退出。 ——因為用new Thread創建線程成本高,所以我們不能把它當快餐線程,所以我們 得讓它瘋狂的循環處理事務,又因為它在瘋狂的循環,所以我們得用中斷的方式 讓它退出來。。。阿門 */ class InterrupThreadExit{ public static void test() { /* 沒有調用可中斷方法的,可以利用檢測標誌位。。。 */ Thread t = new Thread(){ public void run(){ System.out.println("I will start work"); while(!isInterrupted()){ //working } System.out.println("I will be exiting..."); } }; /* 調用了可中斷方法的,可以通過捕獲中斷異常。。。 */ Thread t2 = new Thread(){ public void run(){ System.out.println("I will start work"); while(true){ try{ TimeUnit.MILLISECONDS.sleep(1); } catch (InterruptedException e) { break; } } System.out.println("I will be exiting..."); } }; t.start(); try { TimeUnit.MINUTES.sleep(1); System.out.println("System will be shutdown."); t.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } } } /* 3.使用volatile開關控制 由於interrupt標識很有可能白擦除,或者邏輯單元中不會調用任何可中斷方法,所以使用 volatile修飾的開關flag關閉線程也是一種常用的做法: */ class FlagThreadExit{ static class MyTask extends Thread{ private volatile boolean close = false; @Override public void run() { System.out.println("I will start work"); while(close&&!isInterrupted()){ //working } System.out.println("I will be exiting."); } public void close(){ this.close=true; this.interrupt(); } } public static void test() { MyTask t = new MyTask(); t.start(); try { TimeUnit.MINUTES.sleep(1); System.out.println("sys will be shutdown."); t.close(); } catch (InterruptedException e) { e.printStackTrace(); } } } /* 3.9.2 異常退出 在一個線程的執行單元中,是不允許拋出checked異常的。如果線程在運行過程中需要捕獲 checked異常並且判斷是否還有運行下去的必要,那麽此時可以將checked異常封裝封裝 成unchecked異常拋出,進而結束線程的生命周期。 3.9.3 進程假死 進程出現假死,可能是某個線程阻塞了,或者線程出現了死鎖的情況。可以利用jstack、 jconsole、jvisualvm等工具進行診斷。 */

——《Java高並發編程詳解》

Thread API的詳細介紹