|
好程序員Java培訓(xùn)分享Java多線程并發(fā),1 Java 線程實(shí)現(xiàn)/創(chuàng)建方式 繼承 Thread 類 Thread 類本質(zhì)上是實(shí)現(xiàn)了 Runnable 接口的一個(gè)實(shí)例,代表一個(gè)線程的實(shí)例。啟動(dòng)線程的唯一方法就是通過(guò) Thread 類的 start()實(shí)例方法。start()方法是一個(gè) native 方法,它將啟動(dòng)一個(gè)新線程,并執(zhí)行 run()方法。 public class MyThread extends Thread { public void run() { System.out.println("MyThread.run()"); } } MyThread myThread1 = new MyThread(); myThread1.start(); 實(shí)現(xiàn) Runnable 接口 如果自己的類已經(jīng) extends 另一個(gè)類,就無(wú)法直接 extends Thread,此時(shí),可以實(shí)現(xiàn)一個(gè)Runnable 接口。 public class MyThread extends OtherClass implements Runnable { public void run() { System.out.println("MyThread.run()"); } } //啟動(dòng) MyThreadMyThread myThread = new MyThread(); Thread thread = new Thread(myThread); thread.start(); target.run()public void run() { if (target != null) { target.run(); } } ExecutorService、Callable、Future 有返回值線程 有返回值的任務(wù)必須實(shí)現(xiàn) Callable 接口,類似的,無(wú)返回值的任務(wù)必須 Runnable 接口。執(zhí)行Callable 任務(wù)后,可以獲取一個(gè) Future 的對(duì)象,在該對(duì)象上調(diào)用 get 就可以獲取到 Callable 任務(wù)返回的 Object 了,再結(jié)合線程池接口 ExecutorService 就可以實(shí)現(xiàn)傳說(shuō)中有返回結(jié)果的多線程了。 //創(chuàng)建一個(gè)線程池 ExecutorService pool = Executors.newFixedThreadPool(taskSize); // 創(chuàng)建多個(gè)有返回值的任務(wù) List<Future> list = new ArrayList<Future>(); for (int i = 0; i < taskSize; i++) { Callable c = new MyCallable(i + " "); // 執(zhí)行任務(wù)并獲取 Future 對(duì)象 Future f = pool.submit(c); list.add(f); } // 關(guān)閉線程池pool.shutdown(); // 獲取所有并發(fā)任務(wù)的運(yùn)行結(jié)果for (Future f : list) { // 從 Future 對(duì)象上獲取任務(wù)的返回值,并輸出到控制臺(tái)System.out.println("res:" + f.get().toString()); } 基于線程池的方式 線程和數(shù)據(jù)庫(kù)連接這些資源都是非常寶貴的資源。如果每次需要的時(shí)候創(chuàng)建,不需要的時(shí)候銷毀,是非常浪費(fèi)資源的。那么我們就可以使用緩存的策略,也就是使用線程池。 // 創(chuàng)建線程池ExecutorService threadPool = Executors.newFixedThreadPool(10); while(true) { threadPool.execute(new Runnable() { // 提交多個(gè)線程執(zhí)行 @Override public void run() { System.out.println(Thread.currentThread().getName() + " is running .."); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } }); }} 2 同步鎖與死鎖 同步鎖當(dāng)多個(gè)線程同時(shí)訪問(wèn)同一個(gè)數(shù)據(jù)時(shí),很容易出現(xiàn)問(wèn)題。為了避免這種情況出現(xiàn),我們要保證線程同步互斥,就是指并發(fā)執(zhí)行的多個(gè)線程,在同一時(shí)間內(nèi)只允許一個(gè)線程訪問(wèn)共享數(shù)據(jù)。Java中可以使用synchronized關(guān)鍵字來(lái)取得一個(gè)對(duì)象的同步鎖。 死鎖何為死鎖,就是多個(gè)線程同時(shí)被阻塞,它們中的一個(gè)或者全部都在等待某個(gè)資源被釋放。 3 線程池原理 線程池做的工作主要是控制運(yùn)行的線程的數(shù)量,處理過(guò)程中將任務(wù)放入隊(duì)列,然后在線程創(chuàng)建后啟動(dòng)這些任務(wù),如果線程數(shù)量超過(guò)了最大數(shù)量超出數(shù)量的線程排隊(duì)等候,等其它線程執(zhí)行完畢,再?gòu)年?duì)列中取出任務(wù)來(lái)執(zhí)行。主要特點(diǎn)為:線程復(fù)用;控制最大并發(fā)數(shù);管理線程。 線程復(fù)用一個(gè)Thread的類都有一個(gè)start方法。當(dāng)調(diào)用start啟動(dòng)線程時(shí)Java虛擬機(jī)會(huì)調(diào)用該類的 run 方法。那么該類的 run() 方法中就是調(diào)用了 Runnable 對(duì)象的 run() 方法。我們可以繼承重寫(xiě) Thread 類,在其 start 方法中添加不斷循環(huán)調(diào)用傳遞過(guò)來(lái)的 Runnable 對(duì)象。這就是線程池的實(shí)現(xiàn)原理。循環(huán)方法中不斷獲取 Runnable 是用 Queue 實(shí)現(xiàn)的,在獲取下一個(gè) Runnable 之前可以是阻塞的。 線程池的組成一般的線程池主要分為以下 4 個(gè)組成部分: (1)線程池管理器:用于創(chuàng)建并管理線程池。(2)工作線程:線程池中的線程。(3)任務(wù)接口:每個(gè)任務(wù)必須實(shí)現(xiàn)的接口,用于工作線程調(diào)度其運(yùn)行。(4)任務(wù)隊(duì)列:用于存放待處理的任務(wù),提供一種緩沖機(jī)制。 Java 中的線程池是通過(guò) Executor 框架實(shí)現(xiàn)的,該框架中用到了 Executor,Executors,ExecutorService,ThreadPoolExecutor ,Callable 和 Future、FutureTask 這幾個(gè)類。 ThreadPoolExecutor 的構(gòu)造方法如下: public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize, long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);} corePoolSize:指定了線程池中的線程數(shù)量。 maximumPoolSize:指定了線程池中的最大線程數(shù)量。 keepAliveTime:當(dāng)前線程池?cái)?shù)量超過(guò) corePoolSize 時(shí),多余的空閑線程的存活時(shí)間,即多次時(shí)間內(nèi)會(huì)被銷毀。 unit:keepAliveTime 的單位。 workQueue:任務(wù)隊(duì)列,被提交但尚未被執(zhí)行的任務(wù)。 threadFactory:線程工廠,用于創(chuàng)建線程,一般用默認(rèn)的即可。 handler:拒絕策略,當(dāng)任務(wù)太多來(lái)不及處理,如何拒絕任務(wù)。 拒絕策略線程池中的線程已經(jīng)用完了,無(wú)法繼續(xù)為新任務(wù)服務(wù),同時(shí),等待隊(duì)列也已經(jīng)排滿了,再也塞不下新任務(wù)了。這時(shí)候我們就需要拒絕策略機(jī)制合理的處理這個(gè)問(wèn)題。 JDK 內(nèi)置的拒絕策略如下: AbortPolicy :直接拋出異常,阻止系統(tǒng)正常運(yùn)行。 CallerRunsPolicy :只要線程池未關(guān)閉,該策略直接在調(diào)用者線程中,運(yùn)行當(dāng)前被丟棄的任務(wù)。顯然這樣做不會(huì)真的丟棄任務(wù),但是,任務(wù)提交線程的性能極有可能會(huì)急劇下降。 DiscardOldestPolicy :丟棄最老的一個(gè)請(qǐng)求,也就是即將被執(zhí)行的一個(gè)任務(wù),并嘗試再次提交當(dāng)前任務(wù)。 DiscardPolicy :該策略默默地丟棄無(wú)法處理的任務(wù),不予任何處理。如果允許任務(wù)丟失,這是最好的一種方案。 以上內(nèi)置拒絕策略均實(shí)現(xiàn)了 RejectedExecutionHandler 接口,若以上策略仍無(wú)法滿足實(shí)際需要,完全可以自己擴(kuò)展 RejectedExecutionHandler 接口。 Java 線程池工作過(guò)程(1)線程池剛創(chuàng)建時(shí),里面沒(méi)有一個(gè)線程。任務(wù)隊(duì)列是作為參數(shù)傳進(jìn)來(lái)的。不過(guò),就算隊(duì)列里面有任務(wù),線程池也不會(huì)馬上執(zhí)行它們。 (2)當(dāng)調(diào)用 execute() 方法添加一個(gè)任務(wù)時(shí),線程池會(huì)做如下判斷: a) 如果正在運(yùn)行的線程數(shù)量小于 corePoolSize,那么馬上創(chuàng)建線程運(yùn)行這個(gè)任務(wù); b) 如果正在運(yùn)行的線程數(shù)量大于或等于 corePoolSize,那么將這個(gè)任務(wù)放入隊(duì)列; c) 如果這時(shí)候隊(duì)列滿了,而且正在運(yùn)行的線程數(shù)量小maximumPoolSize,那么還是要?jiǎng)?chuàng)建非核心線程立刻運(yùn)行這個(gè)任務(wù); d) 如果隊(duì)列滿了,而且正在運(yùn)行的線程數(shù)量大于或等maximumPoolSize,那么線程池會(huì)拋出異常 RejectExecutionException。 (3)當(dāng)一個(gè)線程完成任務(wù)時(shí),它會(huì)從隊(duì)列中取下一個(gè)任務(wù)來(lái)執(zhí)行。 (4)當(dāng)一個(gè)線程無(wú)事可做,超過(guò)一定的時(shí)間(keepAliveTime)時(shí),線程池會(huì)判斷,如果當(dāng)前運(yùn)行的線程數(shù)大于 corePoolSize,那么這個(gè)線程就被停掉。所以線程池的所有任務(wù)完成后,它最終會(huì)收縮到 corePoolSize 的大小。 |
|
|
來(lái)自: 好程序員IT > 《Java培訓(xùn)教程》