線程的創建方法:
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(進程編號)查看進程堆棧信息。