一、什麼是線程池?
線程池就是在線程使用之前先把線程創建出來,把線程放在一個池子裏面養着,等需要用的時候直接使用,任務完成之後再把線程歸還。
二、線程池有哪些作用
第一:提供響應效率,有了線程池,當有任務的時候,就有線程直接去完成任務,不用調用系統的資源去新建線程
第二:節約系統資源,線程池中的線程是可以回收重複利用的,降低新建和銷燬線程的資源消耗。
第三:提高線程的管理性,我們知道線程是稀缺資源,不能一直無限制的創建,通過使用線程池我們可以進行統一分配、調優和監控
三、線程池的創建方式
Java是天生就支持併發的語言,支持併發意味着多線程,線程的頻繁創建在高併發及大數據量是非常消耗資源的,因爲java提供了線程池。在jdk1.5以前的版本中,線程池的使用是及其簡陋的,但是在JDK1.5後,有了很大的改善。JDK1.5之後加入了java.util.concurrent包,java.util.concurrent包的加入給予開發人員開發併發程序以及解決併發問題很大的幫助。這篇文章主要介紹下併發包下的Executor接口,Executor接口雖然作爲一個非常舊的接口(JDK1.5 2004年發佈),但是很多程序員對於其中的一些原理還是不熟悉,因此寫這篇文章來介紹下Executor接口,同時鞏固下自己的知識。如果文章中有出現錯誤,歡迎大家指出。
Executor框架的最頂層實現是ThreadPoolExecutor類,Executors工廠類中提供的newScheduledThreadPool、newFixedThreadPool、newCachedThreadPool方法其實也只是ThreadPoolExecutor的構造函數參數不同而已。通過傳入不同的參數,就可以構造出適用於不同應用場景下的線程池,那麼它的底層原理是怎樣實現的呢,這篇就來介紹下ThreadPoolExecutor線程池的運行過程。
corePoolSize: 核心池的大小。 當有任務來之後,就會創建一個線程去執行任務,當線程池中的線程數目達到corePoolSize後,就會把到達的任務放到緩存隊列當中
maximumPoolSize: 線程池最大線程數,它表示在線程池中最多能創建多少個線程;
keepAliveTime: 表示線程沒有任務執行時最多保持多久時間會終止。
unit: 參數keepAliveTime的時間單位,有7種取值,在TimeUnit類中有7種靜態屬性:
線程池四種創建方式
Java通過Executors(jdk1.5併發包)提供四種線程池,分別爲:
newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。
newFixedThreadPool 創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。
newScheduledThreadPool 創建一個定長線程池,支持定時及週期性任務執行。
newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。
代碼演示:
創建可緩存的線程池:
package com.junlaninfo.CachedThreadPool; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Created by 輝 on 2020/5/24. * 可緩存線程池 */ public class CachedThreadPool { public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); for (int i=0;i<100;i++){ executorService.execute(new Runnable() { public void run() { //打印出來線程的名字是重複的 System.out.println(Thread.currentThread().getName()); } }); } } }
創建定長線程池
package com.junlaninfo.FixedThreadPool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by 輝 on 2020/5/24.
* 創建一個定長線程池
定長線程池的大小最好根據系統資源進行設置。如Runtime.getRuntime().availableProcessors()
*/
public class FixedThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i=0;i<10;i++){
executorService.execute(new Runnable() {
public void run() {
//通過打印線程的名字我們可以知道,只有三個線程
System.out.println(Thread.currentThread().getName());
}
});
}
}
}
創建定時執行的線程池
package com.junlaninfo.ScheduledThreadPool; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * Created by 輝 on 2020/5/24. * 定時執行的線程池 */ public class ScheduledThreadPool { public static void main(String[] args) { ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); for (int i=0;i<10;i++){ scheduledExecutorService.schedule(new Runnable() { public void run() { System.out.println("當前線程是:"+Thread.currentThread().getName()); } },3, TimeUnit.SECONDS); } } }
創建單一線程的線程池,遵循先進先出的原則,不會造成線程安全的問題
package com.junlaninfo.SingleThreadExecutor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Created by 輝 on 2020/5/24. * 創建一個單線程的線程池,可以保證執行順序 */ public class SingleThreadExecutor { public static void main(String[] args) { ExecutorService executorService = Executors.newSingleThreadExecutor(); executorService.execute(new Runnable() { public void run() { for (int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+"數字是:"+i); } } }); } }
四、重入鎖
鎖作爲併發共享數據,保證一致性的工具,在JAVA平臺有多種實現(如 synchronized 和 ReentrantLock等等 ) 。這些已經寫好提供的鎖爲我們開發提供了便利。
重入鎖,也叫做遞歸鎖,指的是同一線程 外層函數獲得鎖之後 ,內層遞歸函數仍然有獲取該代碼的鎖,但不受影響。
在JAVA環境下 ReentrantLock 和synchronized 都是可重入鎖
代碼演示synchronized的可重入性
package com.junlaninfo.Reentrantlock; /** * Created by 輝 on 2020/5/24. * 演示synchronized的可重入鎖 * set方法和get方法需要this,這把鎖,當訪問get方法的時候,獲取到this鎖,同時get方法內訪問set方法, * 這時候也可以拿到this這把鎖,這就是鎖的可重入 */ public class synchronizedtest implements Runnable { public synchronized void set() { System.out.println("我是set方法"); } public synchronized void get() { set(); System.out.println("我是get方法"); } public void run() { get(); } public static void main(String[] args) { synchronizedtest synchronizedtest = new synchronizedtest(); new Thread(synchronizedtest).start(); new Thread(synchronizedtest).start(); new Thread(synchronizedtest).start(); } }
代碼演示lock鎖的可重入性
package com.junlaninfo.Reentrantlock; import com.sun.org.apache.bcel.internal.generic.NEW; import com.sun.org.apache.xerces.internal.dom.PSVIAttrNSImpl; import java.util.concurrent.locks.ReentrantLock; /** * Created by 輝 on 2020/5/24. * 測驗lock鎖的可重入性 */ public class Locktest implements Runnable { ReentrantLock lock= new ReentrantLock(); public void set() throws InterruptedException { lock.lock();//上鎖 Thread.sleep(2000); System.out.println("我是set方法"); lock.unlock(); } public void get() throws InterruptedException { lock.lock(); Thread.sleep(2000); set(); System.out.println("我是get方法"); lock.unlock(); } public void run() { try { get(); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { Locktest locktest = new Locktest(); new Thread(locktest).start(); new Thread(locktest).start(); } }
五、讀寫鎖
相比Java中的鎖(Locks in Java)裏Lock實現,讀寫鎖更復雜一些。假設你的程序中涉及到對一些共享資源的讀和寫操作,且寫操作沒有讀操作那麼頻繁。在沒有寫操作的時候,兩個線程同時讀一個資源沒有任何問題,所以應該允許多個線程能在同時讀取共享資源。但是如果有一個線程想去寫這些共享資源,就不應該再有其它線程對該資源進行讀或寫(譯者注:也就是說:讀-讀能共存,讀-寫不能共存,寫-寫不能共存)。這就需要一個讀/寫鎖來解決這個問題。Java5在java.util.concurrent包中已經包含了讀寫鎖。
代碼演示:
package com.junlaninfo.ReadWriteLock; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * Created by 輝 on 2020/5/24. */ public class Cache { static Map<String, Object> map = new HashMap<String, Object>(); static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); static Lock r = rwl.readLock(); static Lock w = rwl.writeLock(); // 獲取一個key對應的value public static final Object get(String key) { r.lock(); try { System.out.println("正在做讀的操作,key:" + key + " 開始"); Thread.sleep(100); Object object = map.get(key); System.out.println("正在做讀的操作,key:" + key + " 結束"); System.out.println(); return object; } catch (InterruptedException e) { } finally { r.unlock(); } return key; } // 設置key對應的value,並返回舊有的value public static final Object put(String key, Object value) { w.lock(); try { System.out.println("正在做寫的操作,key:" + key + ",value:" + value + "開始."); Thread.sleep(100); Object object = map.put(key, value); System.out.println("正在做寫的操作,key:" + key + ",value:" + value + "結束."); System.out.println(); return object; } catch (InterruptedException e) { } finally { w.unlock(); } return value; } // 清空所有的內容 public static final void clear() { w.lock(); try { map.clear(); } finally { w.unlock(); } } public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { Cache.put(i + "", i + ""); } } }).start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { Cache.get(i + ""); } } }).start(); } }