Java加強--1.線程,線程池,鎖,底層源碼介紹及應用
併發和並行
並行: 指兩個或多個時間在同一刻發生(同時發生);
併發: 指兩個或多個時間再同一個時間段內發生(同一段時間發生)
線程與進程
進程: 是指一個內存中運行的應用程序,每個進程都有一個獨立的內存空間,一個應用程序可以同時運行多個線程;線程也是程序的一次執行過程,是系統運行程序的基本單位;系統運行一個程序即是一個線程從創建,運行到消亡的過程.
進程: 進程內部的一個獨立執行單元;一個進程可以同時併發的運行多個線程,可以理解爲一個進程便相當於一個單CPU操作系統,而線程便是這個系統中運行的多個任務
區別
進程: 每個進程有獨立的內存空間,進程中的數據存放空間(堆空間和棧空間),是獨立的,至少有一個進程
線程: 堆空間是共享的,棧空間是獨立的,線程消耗資源比進程小的多
線程的兩種模型
用戶級線程(ULT)
用戶程序實現,不依賴操作系統核心;
由第三方拓展的應用來管理線程(自己安裝的軟件的線程);
不需要用戶態/內核態切換,速度快
內核對ULT 無感知,線程阻塞則進程(包括它的所有線程)阻塞
內核級線程(KLT)
系統內核管理線程,有操作系統管理線程;
內核保存線程的狀態和上下文信息,線程阻塞不會引起進程阻塞;
線程的創建,調度和管理由內核完成,效率比ULT要慢,比進程操作快
Jvm線程模型
JVM虛擬機採用的線程模型:內核級線程(KLT)
Java線程創建是依賴於系統內核,通過JVM調用系統庫創建內核線程,內核線程與Java-Thread是1:1 的映射關係
創建線程
繼承Thread
繼承Thread,重寫該類的run方法,該類的run()方法的方法體代表了線程需要完成的任務,調用改子類的start()方法來啓動線程
public class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+"新的線程"+i);
}
}
}
Thread 類常用方法
構造方法:
public Thread() : 創建一個新的線程對象
public Thread(String name): 創建一個指定名字的新的線程對象
public Thread(Runnable target): 分配一個帶有指定目標新的線程對象
public Thread(Runnable target,String name):分配一個帶有指定目標新的線程對象並指定名字
成員方法:
public String getName() :឴ 獲取當前線程名稱
public void setName(String name) :設置線程名稱
public void start() :開啓線程,再新的執行路徑中執行run方法
public static void sleep(long millis) :讓當前線程休眠指定時間,單位:毫秒值
public static Thread currentThread() : ឴獲得執行當前方法的線程對象
實現Runnable
實現Runnable接口,並重寫該接口的run()方法,並創建實現對象,用來作爲Thread的target 創建 Thread對象,調用線程的start()方法啓動線程
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
public class Demo {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr, "子線程");
t.start();
for (int i = 0; i < 20; i++) {
System.out.println("主線程 " + i);
}
}
}
實現Callable
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName()+":正在執行任務");
return "分線程";
}
}
public class Test2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
FutureTask futureTask = new FutureTask(myCallable);
Thread thread = new Thread(futureTask,"線程1");
thread.start();
System.out.println(Thread.currentThread().getName()+":正在執行");
//該方法不能在前程啓動之前執行
String a = (String) futureTask.get();
System.out.println(a);
}
}
//輸出
線程1:正在執行任務
分線程
main:正在執行
分線程
線程池創建
ExecutorService service = Executors.newFixedThreadPool(2);
Runnable task = new Run();
service.submit(task);
線程安全
概述: 兩個或以上的線程再訪問共享資源時仍能得到正確的結果則稱爲線程安全
當我們使用多個線程訪問統一資源的時候,且多個線程對資源有寫的操作,就容易出現線程安全問題;
Java中提供了同步機制(synchronized)來解決多線程併發訪問一個個資源的安全性問題
線程同步
同步代碼塊
synchronized關鍵字可以用於方法中的某個區域,標識對這個區塊的資源實行互斥訪問
同步代碼塊格式
synchronized(同步鎖){
需要同步操作的代碼
}
同步鎖注意事項:
1.鎖對象 可以使任意類型
2.多個線程對象,要使用同一把鎖
public class Test4 {
private static Object lock = new Object();
private static Integer count = 100;
public static void main(String[] args) {
MyThread thread1 = new MyThread("thread1");
MyThread thread2 = new MyThread("thread2");
MyThread thread3 = new MyThread("thread3");
thread1.start();
thread2.start();
thread3.start();
}
static class MyThread extends Thread{
public MyThread(String name){
super(name);
}
@Override
public void run() {
while (true){
synchronized(lock){
if(count > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在售賣:" + count-- );
}
}
if( count < 1) {
break;
}
}
}
}
}
同步方法
概述: 使用synchronized修飾的方法,就叫同步方法,保證A線程執行該方法的時候,其他線程只能在方法外面等着
public synchronized void method(){
可能會產生線程安全問題的代碼
}
同步鎖:對於非static方法,同步鎖就是this;對於static方法,我們使用當前方法所在類的字節碼對象(類名.class)
public class Test5 {
private static Object lock = new Object();
private static Integer count = 100;
public static void main(String[] args) {
MyThread thread1 = new MyThread("thread1");
MyThread thread2 = new MyThread("thread2");
MyThread thread3 = new MyThread("thread3");
thread1.start();
thread2.start();
thread3.start();
}
static class MyThread extends Thread{
public MyThread(String name){
super(name);
}
@Override
public void run() {
while (true){
rob();
if(count < 1){
break;
}
}
}
public static synchronized void rob(){
if(count > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在售賣:" + count-- );
}
}
}
}
鎖機制
java.util.concurrent.locks.Lock 提供了比同步代碼塊和同步方法更加廣泛的鎖定操作,此外,它的功能更加強大,更體現面向對象
Lock常用方法:
public void lock(): 加同步鎖
public void unlock():釋放同步鎖
public class Test6 {
private static Lock lock = new ReentrantLock();
private static Integer count = 100;
public static void main(String[] args) {
MyThread thread1 = new MyThread("thread1");
MyThread thread2 = new MyThread("thread2");
MyThread thread3 = new MyThread("thread3");
thread1.start();
thread2.start();
thread3.start();
}
static class MyThread extends Thread{
public MyThread(String name){
super(name);
}
@Override
public void run() {
while (true){
lock.lock();
if(count > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在售賣:" + count-- );
}
lock.unlock();
if( count < 1) {
break;
}
}
}
}
}
線程的生命週期
線程 | 導致狀態發生條件 |
---|---|
NEW(新建) | 線程剛被創建,但是並未啓動 |
Runnable(可運行) | 線程可以在Java虛擬機中運行的狀態,可能正在運行自己代碼,也可能沒有,這取決於操作系統處理器.一個正在Java虛擬機中執行的線程處於這一狀態. |
Blocked(鎖阻塞) | 當一個線程試圖獲取一個對象鎖,而該對象被其他的線程持有,則該線程進入Blocked狀態;當該線程持有鎖時,該線程將變成Runnable狀態 |
Waiting(無線等待) | 一個線程在等待另一個線程執行一個(喚醒)動作時,該線程進入Waiting狀態.進入這個狀態後是不能自動喚醒的,必須等待另一個線程調用notify或者notifyAll方法才能喚醒 |
Timed Waiting(計時等待) | 同waiting狀態,有幾個方法有超時參數,調用他們將進入Time Waiting狀態.這一狀態將一直保持到超時期滿或者接收到喚醒通知.帶有超時參數常用方法Thread.sleep,Object.wait |
Teminated(被終止) | 因爲run方法正常退出而死亡,或者因爲有捕獲的異常終止了run方法而死亡 |
package cn.gdmcmc.esi.Thread;
/**
* @Description:
* @Author:lighter
* @Date:2020/5/25 9:21
* @Version 1.0
*/
public class Test3 {
private static Object lock = new Object();
private static Object lock2 = new Object();
public static void main(String[] args) throws InterruptedException {
Thread1 thread1 = new Thread1("thread1");
Thread2 thread2 = new Thread2("thread2");
thread2.start();
thread1.start();
//Thread.sleep(10000);
synchronized (lock){
lock.wait();
}
System.out.println(Thread.currentThread().getName()+"等待喚醒後運行");
}
static class Thread1 extends Thread {
public Thread1(String name){
super(name);
}
@Override
public void run() {
try {
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName()+"休眠5秒後運行");
synchronized (lock){
lock.notifyAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class Thread2 extends Thread{
public Thread2(String name){
super(name);
}
@Override
public void run() {
try {
//Thread.sleep(10000);
synchronized (lock){
lock.wait();
//lock.wait(10000);
}
//synchronized (lock2){
//lock2.wait();
//lock2.wait(10000);
//}
System.out.println(Thread.currentThread().getName()+"等待被喚醒後運行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
線程終止的四種方式
run()結束自動終止
線程run()運行結束或異常終止
stop()方法啓用
public class Test8 {
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start();
Thread.sleep(10);
thread.stop();
}
static class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(i);
}
}
}
}
//輸出爲0-257 不一定
volatile標誌位(外部控制的自然死亡)
public class Test9 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thr( "1");
thread.start();
//修改退出標誌,使線程終止
Thr.flag = true;
}
static class Thr extends Thread{
//
private static volatile boolean flag = false;
public Thr(String name){
super(name);
}
@Override
public void run() {
System.out.println("第" + Thread.currentThread().getName() + "個線程創建");
if (flag){
//退出標誌生效位置
try {
int i = 1/0;
} catch (Exception e) {
throw new RuntimeException("中斷進程");
}
}
System.out.println("第" + Thread.currentThread().getName() + "個線程終止");
}
}
}
//輸出
第1個線程創建
Exception in thread "1" java.lang.RuntimeException: 中斷進程
at cn.gdmcmc.esi.Thread.Test9$Thr.run(Test9.java:37)
interrupt()中斷運行態和阻塞態線程
public class Test10 {
public static void main(String[] args) {
Thread thread = new MyThread("1");
thread.start();
thread.interrupt();
}
static class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 線程創建");
boolean b = Thread.currentThread().isInterrupted();
if (b) {
try {
int i = 1 / 0;
} catch (Exception e) {
throw new RuntimeException("線程中斷");
}
}
System.out.println(Thread.currentThread().getName()+" 線程終止");
}
}
}
//輸出
1 線程創建
Exception in thread "1" java.lang.RuntimeException: 線程中斷
at cn.gdmcmc.esi.Thread.Test10$MyThread.run(Test10.java:31)
線程池
概述
其實就是一個容納多個線程的容器,其中的線程可以反覆只用,省去了頻繁的創建線程對象的操作,無需反覆創建線程而消耗過多資源
假設一個服務器完成一項任務所需時間爲:T1 創建線程時間,T2 在線程中執行任務的時間,T3 銷燬線程時間。
如果:T1 + T3 遠大於 T2,則可以採用線程池,以提高服務器性能。
好處:
1. 降低資源消耗。減少了創建和銷燬線程的次數,每個線程都可以被重複利⽤,可執⾏多個任務。
2.提⾼響應速度。當任務到達時,任務可以不需要的等到線程創建就能⽴即執⾏。
3.提⾼線程的可管理性。可以根據系統的承受能⼒,調整線程池中⼯作線線程的數⽬,防⽌因爲消
耗過多的內存,⽽把服務器累趴下(每個線程需要⼤約1MB內存,線程開的越多,消耗的內存也
就越⼤,最後死機)。
線程池的創建方式
Java⾥⾯線程池的頂級接⼝是 java.util.concurrent.Executor ,但是嚴格意義上講 Executor 並不
是⼀個線程池,⽽只是⼀個執⾏線程的⼯具。真正的線程池接⼝是 java.util.concurrent.ExecutorService 。
源碼
public class Test11 {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(2);
Runnable task = new Run();
service.submit(task);
service.submit(task);
service.submit(task);
}
static class Run implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"線程執行");
}
}
}
//輸出
pool-1-thread-1線程執行
pool-1-thread-2線程執行
pool-1-thread-1線程執行
public class Test12 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(2);
Call call = new Call();
Future<String> f = service.submit(call);
service.submit(call);
System.out.println(f.get());
}
static class Call implements Callable{
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName()+"線程運行");
return Thread.currentThread().getName();
}
}
}
//輸出
pool-1-thread-2線程運行
pool-1-thread-1線程運行
pool-1-thread-1
線程池的分類
newCachedThreadPool(可緩存線城池)
是一種線程數量不定的線程池,並且其最大線程數爲Integer.MAX_VALUE,這個數是很大的,一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。但是線程池中的空閒線程都有超時限制,這個超時時長是60秒,超過60秒閒置線程就會被回收。調用execute將重用以前構造的線程(如果線程可用)。這類線程池比較適合執行大量的耗時較少的任務,當整個線程池都處於閒置狀態時,線程池中的線程都會超時被停止。
特點:
1.核心線程數爲0
2.最大線程數爲Integer.MAX_VALUE
3.阻塞隊列是SynchronousQueue
4.非核心線程空閒存活時間爲60秒
工作機制:
1.提交任務
2.因爲沒有核心線程,所以任務直接加到SynchronousQueue隊列
3.判斷是否有空閒線程,如果有,就去取出任務執行
4.如果沒有空閒線程,就新建一個線程執行
5.執行完任務的線程,還可以存活60秒,如果在這期間,接到任務,可以繼續活下去;否則,被銷燬
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
service.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+"線程");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
//輸出
ool-1-thread-5線程
pool-1-thread-2線程
pool-1-thread-3線程
pool-1-thread-7線程
pool-1-thread-8線程
pool-1-thread-4線程
pool-1-thread-1線程
pool-1-thread-6線程
pool-1-thread-9線程
pool-1-thread-10線程
public class Test13 {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
service.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName()+"線程");
} catch (Exception e) {
e.printStackTrace();
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName()+"線程");
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
//輸出
pool-1-thread-1線程
pool-1-thread-1線程
使用場景: 用於併發執行大量短期的小任務。
newFixedThreadPool(定長線程池)
創建一個指定工作線程數量的線程池,每當提交一個任務就創建一個工作線程,當線程 處於空閒狀態時,它們並不會被回收,除非線程池被關閉了,如果工作線程數量達到線程池初始的最大數,則將提交的任務存入到池隊列(沒有大小限制)中。由於newFixedThreadPool只有核心線程並且這些核心線程不會被回收,這樣它更加快速底相應外界的請求。
特點:
1.核心線程數和最大線程數大小一樣
2.沒有所謂的非空閒時間,即keepAliveTime爲0
3.阻塞隊列爲可設置容量隊列LinkedBlockingQueue
工作機制:
1.提交任務
2.如果線程數少於核心線程,創建核心線程執行任務
3.如果線程數等於核心線程,把任務添加到LinkedBlockingQueue阻塞隊列
4.如果線程執行完任務,去阻塞隊列取任務,繼續執行。
ExecutorService service = Executors.newFixedThreadPool(2);
Runnable task = new Run();
service.submit(task);
newFixedThreadPool使用了無界的阻塞隊列LinkedBlockingQueue,如果線程獲取一個任務後,任務的執行時間比較長(示例,睡眠10s),會導致隊列的任務越積越多,導致機器內存使用不停飆升, 最終導致OutOfMemoryError。
FixedThreadPool 適用於處理CPU密集型的任務,確保CPU在長期被工作線程使用的情況下,儘可能的少的分配線程,即適用執行長期的任務。
newScheduledThreadPool(定長線程池)
創建一個線程池,它的核心線程數量是固定的,而非核心線程數是沒有限制的,並且當非核心線程閒置時會被立即回收,它可安排給定延遲後運行命令或者定期地執行。這類線程池主要用於執行定時任務和具有固定週期的重複任務。
特點:
1.最大線程數爲Integer.MAX_VALUE
2.阻塞隊列是DelayedWorkQueue
3.keepAliveTime爲0
4.scheduleAtFixedRate() :按某種速率週期執行
5.scheduleWithFixedDelay():在某個延遲後執行
工作機制:
1.添加一個任務
2.線程池中的線程從 DelayQueue 中取任務
3.線程從 DelayQueue 中獲取 time 大於等於當前時間的task
4.執行完後修改這個 task 的 time 爲下次被執行的時間
5.這個 task 放回DelayQueue隊列中
public class Test14 {
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
//設置延遲2S後執行
service.schedule(new MyRun(),2, TimeUnit.SECONDS);
//設置延遲2S後,每隔1s執行一次
service.scheduleAtFixedRate(new MyRun(),2,1, TimeUnit.SECONDS);
service.schedule(new MyRun(),2, TimeUnit.SECONDS);
service.execute(new MyRun1());
service.execute(new MyRun1());
service.execute(new MyRun1());
service.execute(new MyRun1());
service.execute(new MyRun1());
}
static class MyRun implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+">>>"+System.currentTimeMillis());
}
}
static class MyRun1 implements Runnable{
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}
//輸出
pool-1-thread-1
pool-1-thread-2
pool-1-thread-2
pool-1-thread-1
pool-1-thread-1>>>1591173057219
pool-1-thread-1>>>1591173057219
pool-1-thread-1>>>1591173057219
pool-1-thread-1>>>1591173057219
pool-1-thread-1>>>1591173057219
pool-1-thread-1>>>1591173058207
pool-1-thread-1>>>1591173059205
pool-1-thread-2
pool-1-thread-1>>>1591173060207
pool-1-thread-1>>>1591173061206
pool-1-thread-1>>>1591173062206
pool-1-thread-1>>>1591173063205
使用場景:週期性執行任務的場景,需要限制線程數量的場景
newSingleThreadExecutor(單線程化線程池)
這類線程池內部只有一個核心線程,以無界隊列方式來執行該線程,這使得這些任務之間不需要處理線程同步的問題,它確保所有的任務都在同一個線程中按順序中執行,並且可以在任意給定的時間不會有多個線程是活動的
特點:
1.核心線程數爲1
2.最大線程數也爲1
3.阻塞隊列是LinkedBlockingQueue
4.keepAliveTime爲0
工作機制:
1.提交任務
2.線程池是否有一條線程在,如果沒有,新建線程執行任務
3.如果有,將任務加到阻塞隊列
4.當前的唯一線程,從隊列取任務,執行完一個,再繼續取,一個人(一條線程)夜以繼日地幹活。
public class Test15 {
private static int num = 0;
public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+">>"+(++num));
}
});
}
}
}
//輸出
pool-1-thread-1>>1
pool-1-thread-1>>2
pool-1-thread-1>>3
pool-1-thread-1>>4
pool-1-thread-1>>5
pool-1-thread-1>>6
pool-1-thread-1>>7
pool-1-thread-1>>8
pool-1-thread-1>>9
pool-1-thread-1>>10
//有線程安全
public class Test15 {
private static int num = 0;
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+">>"+(++num));
}
});
}
}
}
//輸出
pool-1-thread-2>>1
pool-1-thread-1>>1
pool-1-thread-3>>2
pool-1-thread-5>>3
pool-1-thread-4>>4
pool-1-thread-6>>5
pool-1-thread-7>>6
pool-1-thread-8>>7
pool-1-thread-10>>8
pool-1-thread-9>>9
使用場景:適用於串行執行任務的場景,一個任務一個任務地執行。
線程池的組成
線程池管理器(ThreadPool)
用於創建並管理線程池,包括 創建線程池,銷燬線程池,添加新任務
工作線程(PoolWorker)
程池中線程,在沒有任務時處於等待狀態,可以循環的執行任務
任務接口(Task)
每個任務必須實現的接口,以供工作線程調度任務的執行,它主要規定了任務的入口,任務執行完後的收尾工作,任務的執行狀態等
任務隊列(taskQueue)
用於存放沒有處理的任務。提供一種緩衝機制
線程池的參數作用
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
corePoolSize: 線程池核心線程數最大值
maximumPoolSize: 線程池最大線程數大小
keepAliveTime: 線程池中非核心線程空閒的存活時間大小
unit: 線程空閒存活時間單位
workQueue: 存放任務的阻塞隊列
threadFactory: 用於設置創建線程的工廠,可以給創建的線程設置有意義的名字,可方便排查問題
handler: 線城池的飽和策略事件,主要有四種類型。
線程池執行任務流程
execute()方法執行
1.提交一個任務,線程池裏存活的核心線程數小於線程數corePoolSize時,線程池會創建一個核心線程去處理提交的任務。
2.如果線程池核心線程數已滿,即線程數已經等於corePoolSize,一個新提交的任務,會被放進任務隊列workQueue排隊等待執行。
3.當線程池裏面存活的線程數已經等於corePoolSize了,並且任務隊列workQueue也滿,判斷線程數是否達到maximumPoolSize,即最大線程數是否已滿,如果沒到達,創建一個非核心線程執行提交的任務。
4.如果當前的線程數達到了maximumPoolSize,還有新的任務過來的話,直接採用拒絕策略處理。
拒絕策略類型
1.AbortPolicy(拋出一個異常,默認的)
2.DiscardPolicy(直接丟棄任務)
3.DiscardOldestPolicy(丟棄隊列裏最老的任務,將當前這個任務繼續提交給線程池)
4.CallerRunsPolicy(交給線程池調用所在的線程進行處理)
線程池異常處理
1.try/catch捕獲異常
2.submit執行,Future.get接受異常
3.重寫TreadPoolExecutor.afterExecute方法,處理傳遞的異常引用
線程池的工作隊列
ArrayBlockingQueue:(有界隊列)是一個用數組實現的有界阻塞隊列,按FIFO排序量
LinkedBlockingQueue:(可設置容量隊列)基於鏈表結構的阻塞隊列,按FIFO排序任務,容量可以選擇進行設置,不設置的話,將是一個無邊界的阻塞隊列,最大長度爲Integer.MAX_VALUE,吞吐量通常要高於ArrayBlockingQuene;newFixedThreadPool線程池使用了這個隊列
DelayQueue:(延遲隊列)是一個任務定時週期的延遲執行的隊列。根據指定的執行時間從小到大排序,否則根據插入到隊列的先後排序。newScheduledThreadPool線程池使用了這個隊列。
PriorityBlockingQueue:(優先級隊列)是具有優先級的無界阻塞隊列;
SynchronousQueue:(同步隊列)一個不存儲元素的阻塞隊列,每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態,吞吐量通常要高於LinkedBlockingQuene,newCachedThreadPool線程池使用了這個隊列。
線程池的狀態
RUNNING
1.該狀態的線程池會接收新任務,並處理阻塞隊列中的任務;
2.調用線程池的shutdown()方法,可以切換到SHUTDOWN狀態;
3.調用線程池的shutdownNow()方法,可以切換到STOP狀態;
SHUTDOWN
1.該狀態的線程池不會接收新任務,但會處理阻塞隊列中的任務;
2.隊列爲空,並且線程池中執行的任務也爲空,進入TIDYING狀態;
STOP
1.該狀態的線程不會接收新任務,也不會處理阻塞隊列中的任務,而且會中斷正在運行的任務;
2.線程池中執行的任務爲空,進入TIDYING狀態;
TIDYING
1.該狀態表明所有的任務已經運行終止,記錄的任務數量爲0。
2.terminated()執行完畢,進入TERMINATED狀態
TERMINATED
1.該狀態表示線程池徹底終止
源碼分析
run()
Thread(class)
//私有成員變量
private Runnable target;
//構造方法
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
//init
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
//init
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
@Override
public void run() {
if (target != null) {
target.run();
}
}
Runnable(interface)
public abstract void run();
備註: 當實現Runnable 接口創建線程, target 不爲空,線程執行的 Runnable重寫的 run方法;
當繼承Thread 父類,target 爲null,線程執行的Thread重寫的run方法
當繼承Thread和重寫Runnable 一起創建線程時,線程執行是繼承Thread重寫的run方法
public class Test7 {
public static void main(String[] args) {
//匿名內部類
/*
已經重寫了父類的方法,就不會運行這一部分的代碼,就不會運行Runnbale中實現的代碼
if (target != null) {
target.run();
}
*/
new Thread(() ->{
System.out.println("執行Runnable重寫的run()");
}){
@Override
public void run() {
System.out.println("執行Thread重寫的run()");
}
}.start();
}
}
//輸出
System.out.println("執行Thread重寫的run()");
public class Test7 {
public static void main(String[] args) {
Runnable target = new MyRunnable();
Thread thread = new MyThread(target);
thread.start();
}
static class MyThread extends Thread{
private Runnable target;
public MyThread(Runnable target) {
this.target = target;
}
@Override
public void run() {
if (target != null) {
target.run();
}
System.out.println("執行Thread重寫的run()");
}
}
static class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("執行Runnable重寫的run()");
}
}
}
//輸出
執行Runnable重寫的run()
執行Thread重寫的run()
start()
public synchronized void start() {
//一個線程只能運行一次,如果再運行,threadStatus!= 0 ,拋異常
if (threadStatus != 0)
throw new IllegalThreadStateException();
//添加進線程組
group.add(this);
boolean started = false;
try {
//調用native方法執行線程run方法
start0();
started = true;
} finally {
try {
if (!started) {
//啓動失敗,從線程組移除當前線程
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void star0();
線程拓展
join():順序執行
thread.Join把指定的線程加入到當前線程,可以將兩個交替執行的線程合併爲順序執行的線程。
比如在線程B中調用了線程A的Join()方法,直到線程A執行完畢後,纔會繼續執行線程B。
方式一:
Thread t1 = new Thread(new Runnable() {
@Override public void run() {
System.out.println("t1 is finished");
}
});
Thread t2 = new Thread(new Runnable() {
@Override public void run() {
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2 is finished");
}
});
Thread t3 = new Thread(new Runnable() {
@Override public void run() {
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t3 is finished");
}
});
t3.start();
t2.start();
t1.start();
方式二:
Runnable runnable = new Runnable() {
@Override public void run() {
System.out.println(Thread.currentThread().getName() + "執行完成");
}
};
Thread t1 = new Thread(runnable, "t1");
Thread t2 = new Thread(runnable, "t2");
Thread t3 = new Thread(runnable, "t3");
try {
t1.start();
t1.join();
t2.start();
t2.join();
t3.start();
t3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized和ReentrantLock的區別
synchronized是和if、else、for、while一樣的關鍵字,ReentrantLock是類,這是二者的本質區別。既然ReentrantLock是類,那麼它就提供了比synchronized更多更靈活的特性,可以被繼承、可以有方法、可以有各種各樣的類變量,ReentrantLock比synchronized的擴展性體現在幾點上:
(1)ReentrantLock可以對獲取鎖的等待時間進行設置,這樣就避免了死鎖
(2)ReentrantLock可以獲取各種鎖的信息
(3)ReentrantLock可以靈活地實現多路通知
ReadWriteLock接口
讀寫鎖:
允許多個用戶讀,但只允許一個用戶寫,以此來保持它的完整性;
首先明確一下,不是說ReentrantLock不好,只是ReentrantLock某些時候有侷限。如果使用ReentrantLock,可能本身是爲了防止線程A在寫數據、線程B在讀數據造成的數據不一致,但這樣,如果線程C在讀數據、線程D也在讀數據,讀數據是不會改變數據的,沒有必要加鎖,但是還是加鎖了,降低了程序的性能。
因爲這個,才誕生了讀寫鎖ReadWriteLock。ReadWriteLock是一個讀寫鎖接口,ReentrantReadWriteLock是ReadWriteLock接口的一個具體實現,實現了讀寫的分離,讀鎖是共享的,寫鎖是獨佔的,讀和讀之間不會互斥,讀和寫、寫和讀、寫和寫之間纔會互斥,提升了讀寫的性能
public class Test16 {
public static void main(String[] args) {
MyLock mylock = new MyLock();
ExecutorService service = Executors.newCachedThreadPool();
ExecutorService service1 = Executors.newFixedThreadPool(20);
for (int i = 0; i < 20; i++) {
String I = String.valueOf(i);
if(i ==2 || i == 4){
service1.execute(()->{
mylock.put(I);
});
}else {
service1.execute(()->{
mylock.get();
});
}
}
service1.shutdown();
}
static class MyLock{
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock newLock = new ReentrantLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
private List<String> list = new ArrayList<>();
public void put(String str){
writeLock.lock();
System.out.println(Thread.currentThread().getName()+"put,拿到鎖");
try {
Thread.sleep(5000);
list.add(str);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
writeLock.unlock();
System.out.println(Thread.currentThread().getName()+"put,釋放鎖");
}
}
public void get(){
readLock.lock();
System.out.println(Thread.currentThread().getName()+"get,拿到鎖");
try {
System.out.println(list);
} catch (Exception e) {
e.printStackTrace();
} finally {
readLock.unlock();
System.out.println(Thread.currentThread().getName()+"get,釋放鎖");
}
}
}
}
wait()和sleep()方法的不同
最大的不同是在等待時wait會釋放鎖,而sleep一直持有鎖。Wait通常被用於線程間交互,sleep通常被用於暫停執行。
1.sleep方法是Thread類的靜態方法,wait()是Object超類的成員方法
2.sleep()方法導致了程序暫停執行指定的時間,讓出cpu該其他線程,但是他的監控狀態依然保持者,當指定的時間到了又會自動恢復運行狀態。在調用sleep()方法的過程中,線程不會釋放對象鎖。
而當調用wait()方法的時候,線程會放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象調用notify()方法後本線程才進入對象鎖定池準備
3.sleep方法需要拋異常,wait方法不需要
4.sleep方法可以在任何地方使用,wait方法只能在同步方法和同步代碼塊中使用
wait()/notify()/nitifyAll()
1.wait()使當前線程阻塞,前提是 必須先獲得鎖,一般配合synchronized 關鍵字使用,即,一般在synchronized 同步代碼塊裏使用 wait()、notify/notifyAll() 方法
2.由於 wait()、notify/notifyAll() 在synchronized 代碼塊執行,說明當前線程一定是獲取了鎖的。
當線程執行wait()方法時候,會釋放當前的鎖,然後讓出CPU,進入等待狀態。
只有當 notify/notifyAll() 被執行時候,纔會喚醒一個或多個正處於等待狀態的線程,然後繼續往下執行,直到執行完synchronized 代碼塊的代碼或是中途遇到wait() ,再次釋放鎖。
也就是說,notify/notifyAll() 的執行只是喚醒沉睡的線程,而不會立即釋放鎖,鎖的釋放要看代碼塊的具體執行情況。所以在編程中,儘量在使用了notify/notifyAll() 後立即退出臨界區,以喚醒其他線程讓其獲得鎖
3.wait() 需要被try catch包圍,以便發生異常中斷也可以使wait等待的線程喚醒。
4.notify 和wait 的順序不能錯,如果A線程先執行notify方法,B線程在執行wait方法,那麼B線程是無法被喚醒的。
5.notify方法只喚醒一個等待(對象的)線程並使該線程開始執行。所以如果有多個線程等待一個對象,這個方法只會喚醒其中一個線程,選擇哪個線程取決於操作系統對多線程管理的實現。notifyAll 會喚醒所有等待(對象的)線程,儘管哪一個線程將會第一個處理取決於操作系統的實現。如果當前情況下有多個線程需要被喚醒,推薦使用notifyAll 方法。比如在生產者-消費者裏面的使用,每次都需要喚醒所有的消費者或是生產者,以判斷程序是否可以繼續往下執行。
6.要注意,notify喚醒沉睡的線程後,線程會接着上次的執行繼續往下執行。所以在進行條件判斷時候,可以先把 wait 語句忽略不計來進行考慮;顯然,要確保程序一定要執行,並且要保證程序直到滿足一定的條件再執行,要使用while進行等待,直到滿足條件才繼續往下執行。如下代碼:
public class K {
//狀態鎖
private Object lock;
//條件變量
private int now,need;
public void produce(int num){
//同步
synchronized (lock){
//當前有的不滿足需要,進行等待,直到滿足條件
while(now < need){
try {
//等待阻塞
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我被喚醒了!");
}
// 做其他的事情
}
}
}
if/while 區別
只有當前值滿足需要值的時候,線程纔可以往下執行,所以,必須使用while 循環阻塞。注意,wait() 當被喚醒時候,只是讓while循環繼續往下走.如果此處用if的話,意味着if繼續往下走,會跳出if語句塊。
public class Test18 {
private static Object lock = new Object();
private static Integer num = 10;
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
Thread thread1 = new Thread1("thread1");
Thread thread2 = new Thread2("thread2");
service.scheduleAtFixedRate(()->{
--num;
synchronized (lock){
lock.notifyAll();
}
},1,1, TimeUnit.SECONDS);
thread1.start();
thread2.start();
}
static class Demo1{
public static void whileDemo(){
synchronized (lock){
while (num > 5){
System.out.println(Thread.currentThread().getName()+"開始執行,num:"+num);
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"被喚醒,num:"+num);
}
}
}
static class Thread1 extends Thread{
public Thread1(String name) {
super(name);
}
@Override
public void run() {
Demo1.whileDemo();
}
}
static class Demo2{
public static void ifDemo(){
synchronized (lock){
if (num > 5){
System.out.println(Thread.currentThread().getName()+"開始執行,num:"+num );
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"被喚醒,num:"+num);
}
}
}
static class Thread2 extends Thread{
public Thread2(String name) {
super(name);
}
@Override
public void run() {
Demo2.ifDemo();
}
}
}
//輸出
thread2開始執行,num:10
thread1開始執行,num:10
thread1開始執行,num:9
thread2被喚醒,num:9
thread1開始執行,num:8
thread1開始執行,num:7
thread1開始執行,num:6
thread1被喚醒,num:5
if判斷,被喚醒後直接順序執行,然後線程執行完畢就停止了
while判斷,只要條件成立,就一直執行,形成阻塞,然後知道條件不成立,跳出循環,線程執行完畢
線程實現生產者和消費者
public class Test19 {
public static void main(String[] args) {
AbstractStorage storage = new Storage();
ExecutorService service = Executors.newCachedThreadPool();
//消費
service.execute(new Produce(10, storage));
service.execute(new Produce(20, storage));
service.execute(new Produce(30, storage));
service.execute(new Produce(40, storage));
service.execute(new Produce(50, storage));
service.execute(new Produce(60, storage));
service.execute(new Produce(80, storage));
//生產
service.execute(new Consumer(60,storage));
service.execute(new Consumer(50,storage));
service.execute(new Consumer(40,storage));
service.execute(new Consumer(30,storage));
service.execute(new Consumer(10,storage));
service.execute(new Consumer(20,storage));
service.execute(new Consumer(80,storage));
service.shutdown();
}
interface AbstractStorage{
void consume(Integer num);
void produce(Integer num);
}
static class Storage implements AbstractStorage{
//倉庫容量
private static final int MAX_SIZE = 100;
//倉庫存儲的載體
private List list = new LinkedList();
@Override
public void consume(Integer num) {
synchronized (list){
while (list.size() + num > MAX_SIZE){
System.out.println("待生產量:"+ num + "+ 庫存量:" + list.size() + ">最大容量,暫不能生產");
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (Integer i = 0; i < num; i++) {
list.add(new Object());
}
System.out.println("已生產:"+num +",現倉庫容量:"+list.size());
list.notifyAll();
}
}
@Override
public void produce(Integer num) {
synchronized (list){
while (num > list.size()){
System.out.println("待消費量:" + num +"> 庫存量:"+list.size() + " 暫不能消費" );
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (Integer i = 0; i < num; i++) {
list.remove(0);
}
System.out.println("已經消費:" + num +",現庫存量:"+list.size());
list.notifyAll();
}
}
}
//消費者
static class Produce implements Runnable{
private Integer num;
private AbstractStorage storage;
public Produce(){
}
public Produce(Integer num, AbstractStorage storage) {
this.num = num;
this.storage = storage;
}
@Override
public void run() {
storage.produce(this.num);
}
}
//生產者
static class Consumer implements Runnable{
private Integer num;
private AbstractStorage storage;
public Consumer(){
}
public Consumer(Integer num, AbstractStorage storage) {
this.num = num;
this.storage = storage;
}
@Override
public void run() {
storage.consume(this.num);
}
}
}
//輸出
待消費量:20> 庫存量:0 暫不能消費
待消費量:50> 庫存量:0 暫不能消費
待消費量:80> 庫存量:0 暫不能消費
待消費量:40> 庫存量:0 暫不能消費
待消費量:30> 庫存量:0 暫不能消費
待消費量:10> 庫存量:0 暫不能消費
待消費量:60> 庫存量:0 暫不能消費
已生產:60,現倉庫容量:60
已經消費:60,現庫存量:0
待消費量:10> 庫存量:0 暫不能消費
已生產:10,現倉庫容量:10
待消費量:30> 庫存量:10 暫不能消費
待消費量:40> 庫存量:10 暫不能消費
待消費量:80> 庫存量:10 暫不能消費
待消費量:50> 庫存量:10 暫不能消費
待消費量:20> 庫存量:10 暫不能消費
已生產:50,現倉庫容量:60
已經消費:20,現庫存量:40
待消費量:50> 庫存量:40 暫不能消費
待消費量:80> 庫存量:40 暫不能消費
已經消費:40,現庫存量:0
待消費量:30> 庫存量:0 暫不能消費
已生產:80,現倉庫容量:80
已生產:20,現倉庫容量:100
已經消費:10,現庫存量:90
待生產量:30+ 庫存量:90>最大容量,暫不能生產
待生產量:40+ 庫存量:90>最大容量,暫不能生產
已經消費:30,現庫存量:60
待消費量:80> 庫存量:60 暫不能消費
已經消費:50,現庫存量:10
待消費量:80> 庫存量:10 暫不能消費
已生產:40,現倉庫容量:50
已生產:30,現倉庫容量:80
已經消費:80,現庫存量:0
自己調節生產和消費總數目,發現生產數量 >= 消費數量,線程能全不執行完畢,生產數量 < 消費數量,消費者線程有一個沒有執行完畢,還是在等待狀態