多線程的創建方法 / callable / 線程池 / ThreadPoolExecutor / 必定產生死鎖的代碼 /死鎖產生及排查

線程的創建方法:
1、繼承Thread
2、實現Runnable
3、實現callable
4、線程池
多線程實現之Callable與Runnable的使用:
區別1:Callable有返回值,Runnable沒有返回值。
區別2: Callable會拋出異常,Runnable不會拋出異常。
區別3: 實現接口不一樣
第3種 實現callable接口
Callable 實現是,用futureTask來實現調用(構造注入,接口編程)。

package com.lm.Thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class MyThread implements Callable<Integer>{
     @Override
     public Integer call() throws Exception {
         System.out.println("callable is invode...thread is "+ Thread.currentThread().getName()+"--id:"+Thread.currentThread().getId());
         return 1024;
     }
     
} 
/**
 * @author: mulming
 * @ClassName: CallableDemo
 * @date: 2019年5月11日 下午3:07:42
 * @Description:TODO(這裏用一句話描述這個類的作用)
 */
public class CallableDemo {
     public static void main(String[] args) {
         FutureTask<Integer> my=new FutureTask<>(new MyThread());
         Thread t1=new Thread(my,"AAA");
         t1.start();
         /*while(!my.isDone()) {
             
         }*/
         try {
             System.out.println(my.get());//返回值,返回給主線程
         } catch (InterruptedException | ExecutionException e) {
             e.printStackTrace();
         }
     }
}

第4種 使用線程池
爲什麼用線程池,優勢?
答:線程池做的工作主要是控制運行的線程的數量,處理過程中將任務放入隊列,然後在線程創建後啓動這些任務,如果線程數量超過了最大數量,超出的數量的線程排隊等候,等其他線程執行完畢,再從隊列中取出任務來執行。
特點:線程複用;控制最大併發數,管理線程
第一:降低資源消耗。通過重複利用已創建的線程降低線程創建和銷燬造成的消耗。
第二:提高響應速度。當任務到達時,任務可以不需要的等到線程創建就能立即執行。
第三:提高線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。
線程池如何使用?
Java中的線程池是通過Executor框架實現的,該框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor這幾個類。
使用線程的主要的3個方法:(底層都是使用ThreadPoolExecutor實現)
Executors.newFixedThreadPool(int)—一池固定數線程。執行長期的任務,性能好 ===數據結構:LinkedBlockingQueue
Executors.newSingleThreadExecutor()—一池一線程。一個任務一個任務執行的場景 ===數據結構:LinkedBlockingQueue
Executors.newCachedThreadPool()—一池多線程。執行很多短期異步的小程序或者負載較輕的服務器,自動加載多個 ===數據結構:SynchronousQueue
3種基礎使用方法:

package com.lm.Thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * @author: mulming
 * @ClassName: MyThreadPoolDemo
 * @date: 2019年5月11日 下午3:54:37
 * @Description:第4中獲得/使用Java多線程的方式
 */
public class MyThreadPoolDemo {
     public static void main(String[] args) {
         
         //ExecutorService threadPool=Executors.newFixedThreadPool(5);//一池5個處理線程
         //ExecutorService threadPool=Executors.newSingleThreadExecutor();//一池1個處理線程
         ExecutorService threadPool=Executors.newCachedThreadPool();//一池n個處理線程
         try {
             for (int i = 1; i <=10; i++) {
                 threadPool.execute(()->{
                     System.out.println(Thread.currentThread().getName());
                 });
             }
             
         } catch (Exception e) {
             // TODO: handle exception
         }finally {
             threadPool.shutdown();
         }
     }
}

線程池底層源碼和7大參數:
1、corePoolSize :線程池中常駐核心線程數
== 1)、在創建了線程池後,當有請求任務來之後,就會安排池中的線程去執行請求任務,近似理解爲今日當值線程;
== 2)、當線程池中的線程數目達到corePoolSize後,就會把到達的任務放到緩存隊列當中;
2、maximumPoolSize: 線程池能夠容納同時執行的最大線程數,此值必須大於等於1
3、keepAliveTime:多餘的空閒線程的存活時間。 當前線程池數量超過corePoolSize時,當空閒時間達到KeepAliveTime值時,多餘空閒線程會被銷燬直到只剩下corePoolSize個線程爲止。
==1)、默認情況下:只有當線程池中的線程數大於corePoolSize時keepAliveTime纔會起作用,直到線程池中的線程數不大於corePoolSize
4、unit:keepAliveTime的單位
5、workQueue:任務隊列,被提交但尚未被執行的任務
== 阻塞隊列,相當於緩存區
6、threadFactory:表示生成線程池中工作線程的線程工廠,用於創建線程,一般用默認的即可
7、handler:拒絕策略,表示當隊列滿了並且工作線程大於等於線程池的最大線程數(maximumPoolSize)時如何來拒絕
JDK內置的4種拒絕策略:
AbortPolicy:默認的。直接拋出RejectedExecutionException異常阻止系統正常運行。
CallerRunsPolis:“調用者運行”一種調節機制,該策略既不會拋棄任務,也不會拋出異常,而是將某些任務回退到調用用者,從而降低新任務的流量。
DiscardOldestPolicy:拋棄隊列中等待最久的任務,然後把當前任務加入隊列中嘗試再次提交當前任務。
DiscardPolicy :直接丟棄任務,不予任何處理也不拋出異常。如果允許任務丟失,這是最好的一種方案
線程池的底層工作原理?
1.在創建了線程池後,等待提交過來的任務請求
2.當調用execute()方法添加一個請求任務時,線程池會做如下判斷:
2.1 如果正在運行的線程數量小於corePoolSize,那麼馬上創建線程運行這個任務
2.2 如果正在運行的線程數量大於或等於corePoolSize,那麼將這個任務放入隊列
2.3 如果這時候隊列滿了,且正在運行的線程數量還小於maximumPoolSize,那麼還是要創建非核心線程立刻運行這個任務
2.4 如果隊列滿了,且正在運行的線程數量大於或等於maximumPoolSize,那麼線程池會飽和和拒絕策略來執行
3.當一個線程完成任務時,它會從隊列取下一個任務來執行。
4.當一個線程無事可做超過一定的時間(keepAliveTime)時,線程池會判斷:
如果當前運行的線程數量大於corePoolSize,那麼這個線程就被停掉。
所以線程池的所有任務完成後,它最終會收縮到corePoolSize的大小。
開發中 單一/固定/可變的三種創建線程池的方法,你用那個?
答: 因爲自定義的LinkblockQueue最大時integer.max. 太大了,而一池多程的定義的最大線程數太多。
在開發中我們進行自定義,使用 ThreadPoolExecutor 進行new

    public static void main(String[] args) {
         //自定義ThreadPoolExecutor
         ExecutorService th=new ThreadPoolExecutor(
                 2, 
                 Runtime.getRuntime().availableProcessors()+1,
                 1l, 
                 TimeUnit.SECONDS, 
                 new LinkedBlockingQueue<Runnable>(3), 
                 Executors.defaultThreadFactory(), 
                 new ThreadPoolExecutor.CallerRunsPolicy());//此處自定義可以是四種
         try {
             for (int i = 1; i <=11; i++) {
                 th.execute(()->{
                     try {
                         TimeUnit.SECONDS.sleep(1);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                     System.out.println(Thread.currentThread().getName());
                 });
             }
         } catch (Exception e) {
             e.printStackTrace();
         }finally {
             th.shutdown();
         }
     }

死鎖編碼及定位分析:
是什麼?
死鎖是指2個或2個以上的進程在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力干涉那麼它們都將無法推進下去,如果系統資源充足,進程的資源請求都能夠得到滿足,死鎖出現的可能性就很低,否則就會因爭奪有限的資源而陷入死鎖。

代碼?

package com.lm.Thread;
import java.util.concurrent.TimeUnit;
class HoldLockThread implements Runnable{
     private String lockA;
     private String lockB;
     
     public HoldLockThread(String lockA, String lockB) {
         this.lockA = lockA;
         this.lockB = lockB;
     }
     @Override
     public void run() {
         synchronized (lockA) {
             System.out.println(Thread.currentThread().getName()+"\t 自己持有:"+lockA+"\t 嘗試獲得:"+lockB);
             try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}
             synchronized (lockB) {
                 System.out.println(Thread.currentThread().getName()+"\t 自己持有:"+lockB+"\t 嘗試獲得:"+lockA);
             }
         }
     }
}
/**
 * @author: mulming
 * @ClassName: DeadLockDemo
 * @date: 2019年5月13日 上午9:59:55
 * @Description:必須產生死鎖的代碼
 */
public class DeadLockDemo {
     
     public static void main(String[] args) {
         String lockA="locka";
         String lockB="lockb";
         new Thread(new HoldLockThread(lockA, lockB),"ThrAAA").start();
         new Thread(new HoldLockThread(lockB, lockA),"ThrBBB").start();
     }
}

解決?
第1步:jps命令定位進程號。進入當前文件包下,在控制檯使用 jps -l 查看。
第2步:jstack找到死鎖查看。在使用JStack xxx(進程編號)查看進程堆棧信息。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章