Java多線程常見案例分析線程池與單例模式及阻塞隊列
一、單例模式
設(shè)計模式:軟件設(shè)計模式
是一套被反復(fù)使用、多數(shù)人知曉、經(jīng)過分類編目、代碼設(shè)計經(jīng)驗的總結(jié)。使用設(shè)計模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性、程序的重用性。
單例模式:是設(shè)計模式的一種。保證某個類在程序中只存在唯一一份實例,不會創(chuàng)建出多個實例。單例模式的具體實現(xiàn)分為“懶漢”和“餓漢”兩種。
構(gòu)造方法必須是私有的,保證該類不能在類外被隨便創(chuàng)建。
1、餓漢模式
類加載的同時,創(chuàng)建實例。(缺點是無論是否使用都會創(chuàng)建對象,比較占空間)
//類加載的時候創(chuàng)建對象,確保只能有一個實例對象 class Singleton { private static Singleton instance = new Singleton(); //私有的構(gòu)造方法 private Singleton() {} //只能通過getInstance()方法獲取到同一個實例對象 public static Singleton getInstance() { return instance; } }
2、懶漢模式(單線程)
類加載的時候不創(chuàng)建實例,第一次使用的時候才創(chuàng)建實例。
(缺點:線程不安全,如果存在多個線程并發(fā)并行執(zhí)行,可能創(chuàng)建多個實例,所以只適用于單線程)
class Singleton { private static Singleton instance = null; private Singleton() {} public static Singleton getInstance() { //第一次使用時,創(chuàng)建實例 if (instance == null) { instance = new Singleton(); } //后面使用時,直接返回第一次創(chuàng)建的實例 return instance; } }
3、懶漢模式(多線程)
上面的懶漢模式存在線程安全問題,如果多個線程同時調(diào)用getInstance()方法,可能創(chuàng)建多個實例。所以在多線程時,我們需要使用synchronized改善線程安全問題。
class Singleton { private static Singleton instance = null; private Singleton() {} //加鎖保證不會有多個線程同時訪問改代碼塊 public synchronized static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
對于以上代碼,雖然保證了線程安全,但是對于懶漢模式,只有在第一次調(diào)用時才會創(chuàng)建實例,大多數(shù)境況下只進行讀操作,如果對代碼塊整體加鎖,程序執(zhí)行的效率會大大降低。我們可以對上面的程序進一步優(yōu)化,對于讀操作,我們使用volatile修飾變量;只給寫操作的代碼塊加上鎖即可。
【單例模式懶漢模式多線程的進一步優(yōu)化】雙重if判定
class Singleton { //使用volatile修飾變量 private static volatile Singleton instance = null; private Singleton() {}; public static Singleton getInstance() { if (instance == null) { //只給寫操作的相關(guān)代碼加鎖 synchronized (Singleton.class) { //需要雙重if判斷,防止在多線程中加鎖前instance發(fā)生變化 if (instance == null) { instance = new Singleton(); } } } return instance; } }
寫操作加鎖,保證線程安全;
如果已經(jīng)實例化,進行讀操作,保證多個線程并發(fā)并行執(zhí)行,保證效率。
二、阻塞隊列
阻塞隊列是什么?
阻塞隊列是一種特殊的隊列。也遵守“先進先出”的原則。
阻塞隊列是一種線程安全的數(shù)據(jù)結(jié)構(gòu):
- 當(dāng)隊列滿的時候,繼續(xù)入隊隊列就會阻塞,知道有其他線程從隊列中取走元素;
- 當(dāng)隊列空的時候,繼續(xù)出隊也會阻塞,直到其他線程往隊列中插入元素。
阻塞隊列的一個經(jīng)典應(yīng)用場景就是“生產(chǎn)者消費者模型”。
標準庫中的阻塞隊列:
- BlockingQueue是一個接口,真是實現(xiàn)的是類是:LinkedBlockingQueue。
- put方法用于阻塞式的入隊列,take用于阻塞式的出隊列。
- BlockingQueue也有offer、poll、peek方法,但是不具有阻塞特性。
阻塞隊列的實現(xiàn)
- 通過循環(huán)隊列實現(xiàn);
- 使用synchronized進行加鎖控制
- put插入元素,如果隊列滿了,就進行wait(要在循環(huán)中進行wait,多線程情況下可能喚醒多個線程,所以喚醒后隊列可能還是滿的)
- take取出元素,如果隊列為空,就wait(循環(huán)中wait)
public class BlockingQueue{ //使用循環(huán)數(shù)組來實現(xiàn)阻塞隊列 private int[] array; //隊列中已經(jīng)存放元素的個數(shù) private int size; //放入元素的下標 private int putIndex; //取元素的下標 private int takeIndex; //在構(gòu)造方法中指定隊列的大小 public BlockingQueue(int capacity){ array=new int[capacity]; } /*放元素:需要保證線程安全,如果隊列滿了,線程進入等待*/ public synchronized void put(int m) throws InterruptedException { //隊列滿,線程等待 if(size==array.length){ //需要注意的是,進行等待的是當(dāng)顯得實例對象,不是類對象 this.wait(); } //放元素,同時更新下標 array[putIndex]=m; putIndex=(putIndex+1)%array.length; size++; //通知等待的線程 notifyAll(); } /*取元素:保證線程安全。如果隊列為空,線程等待*/ public synchronized int take() throws InterruptedException { //隊列為空,線程等待 if(size==0){ this.wait(); } //取元素,同時更新下標 int ret=array[takeIndex]; takeIndex=(takeIndex+1)%array.length; size--; //通知等待的線程 notifyAll(); return ret; } }
生產(chǎn)者消費者模型
生產(chǎn)者消費者模型就是通過一個容器來解決生產(chǎn)者和消費者之間的強耦合問題。
生產(chǎn)者和消費者之間不直接通信,而通過阻塞隊列來實現(xiàn)通訊,所以生產(chǎn)者生產(chǎn)完數(shù)據(jù)不需要等待消費者來處理,直接扔給阻塞隊列。消費者也不需要去找生產(chǎn)者,而是直接從阻塞隊列中取。
- 阻塞隊列相當(dāng)于一個緩沖區(qū),平衡了消費者和生產(chǎn)者的處理能力;
- 阻塞隊列也能使生產(chǎn)者和消費者之間“解耦”。
耦合和解耦:
- 耦合指的是兩個類之間聯(lián)系的緊密程度。強耦合(表示類之間存在著直接的關(guān)系)。弱耦合(在兩個類的中間加入一層,將原來的之間關(guān)系變成間接關(guān)系,使得兩個類對中間層是強耦合,兩個類之間變成了弱耦合。
- 解耦:降低耦合度,也就是將強耦合變成弱耦合的過程。
三、線程池
池:字符串常量池(類似緩存)、數(shù)據(jù)庫連接池等
線程池:初始化的時候就創(chuàng)建一定數(shù)量的線程【不同的從線程池的阻塞隊列中取任務(wù)(消費者)】【在其他線程中提交任務(wù)到線程池(生產(chǎn)者)】
優(yōu)點:
線程的創(chuàng)建和銷毀都有一定的代價,使用線程池就可以重復(fù)使用線程來執(zhí)行多組任務(wù)。(如果線程不再使用,并不是真正的將線程釋放,而是放到一個“池子”中,下次如果需要用到線程直接從池子中取,不必通過系統(tǒng)來創(chuàng)建)
1、創(chuàng)建線程池的的方法
(1)ThreadPoolExecutor
提供了更多的可選參數(shù),可以進一步細化線程池行為的設(shè)定。
以第三個構(gòu)造方法為例:
- corePoolSize:表示核心線程的數(shù)量
- maximumPoolSize:最大線程數(shù)(核心線程+臨時線程)
- keepAliveTime:允許臨時線程空閑的時間(如果超過該時間臨時線程還是沒有任務(wù)執(zhí)行,就被銷毀)
- unit: keepaliveTime的時間單位
- workQueue:傳遞任務(wù)的阻塞隊列
- threadFactory:規(guī)定創(chuàng)建線程的標準
- RejectedExecutionHandler:拒絕策略,如果阻塞隊列已滿,再傳進來任務(wù)該怎么辦
【1】AbortPolicy():超過負荷,直接拋出異常(默認的拒絕策略,使用其他不帶拒絕策略的構(gòu)造方法時的默認參數(shù))
【2】CallerRunsPolicy():調(diào)用者負責(zé)處理
【3】DiscardOldestPolicy():丟棄隊列中最老的任務(wù)
【4】DiscardPolicy():丟棄新來的任務(wù)
創(chuàng)建線程池如下:
//使用ThreadPoolExecutor創(chuàng)建線程池 ThreadPoolExecutor threadPool1=new ThreadPoolExecutor( 5, 10, 3, //自由線程無任務(wù)時最大存活時間單位:分 TimeUnit.MINUTES, //一般不使用無邊界的阻塞隊列,內(nèi)存有限 new ArrayBlockingQueue<>(100), //規(guī)定創(chuàng)建線程的標準 Executors.defaultThreadFactory(), //拒絕策略:一般最多使用CallerRunsPolicy(),或自己實現(xiàn) new ThreadPoolExecutor.CallerRunsPolicy() );
(2)Executors(快捷創(chuàng)建線程池的API)
Executors創(chuàng)建線程的幾種方式:
- newFixedThreadPool:創(chuàng)建固定線程數(shù)的線程池(沒有臨時線程)
- newCachedThreadPool:創(chuàng)建線程數(shù)目動態(tài)增長的線程池(緩存的線程池,沒有核心線程,全是臨時線程)
- newSingleThreadExecutor:創(chuàng)建只包含單個線程的線程池
- newScheduledThreadPool:設(shè)定延遲時間后執(zhí)行任務(wù),或者定期執(zhí)行命令(計劃線程池)
創(chuàng)建線程池如下:
//Executors的四種創(chuàng)建線程的方法 //沒有臨時線程的線程池 ExecutorService threadPool2= Executors.newFixedThreadPool(10); //線程數(shù)目動態(tài)增長的線程池 ExecutorService threadPool3=Executors.newCachedThreadPool(); //創(chuàng)建單個線程的線程池 ExecutorService threadPool4=Executors.newSingleThreadExecutor(); //計劃線程池 ExecutorService threadPool5=Executors.newScheduledThreadPool(7);
2、線程池的工作流程
線程池工作流程
使用線程池:
創(chuàng)建線程池
提交任務(wù):
【1】submit(Runnable task)
【2】execute(Runnable task)
到此這篇關(guān)于Java多線程常見案例分析線程池與單例模式及阻塞隊列的文章就介紹到這了,更多相關(guān)Java線程池內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java使用JSONPath解析JSON完整內(nèi)容詳解
這篇文章主要介紹了Java使用JSONPath解析JSON完整內(nèi)容詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03mybatis報錯元素內(nèi)容必須由格式正確的字符數(shù)據(jù)或標記組成異常的解決辦法
今天小編就為大家分享一篇關(guān)于mybatis查詢出錯解決辦法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-12-12Josephus環(huán)的四種解法(約瑟夫環(huán))基于java詳解
這篇文章主要介紹了Josephus環(huán)的四種解法(約瑟夫環(huán))基于java詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-09-09Java實現(xiàn)經(jīng)典游戲泡泡堂的示例代碼
這篇文章將利用Java制作經(jīng)典游戲——泡泡堂,游戲設(shè)計為雙人pk積分賽模式,在這個模式里面,玩家只要率先達到一定分數(shù)既可以贏得比賽。感興趣的可以了解一下2022-04-04Java中數(shù)據(jù)庫常用的兩把鎖之樂觀鎖和悲觀鎖
這篇文章主要介紹了數(shù)據(jù)庫常用的兩把鎖之樂觀鎖和悲觀鎖,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07詳解Java如何實現(xiàn)一個像String一樣不可變的類
說到?String?大家都知道?String?是一個不可變的類;雖然用的很多,那不知道小伙伴們有沒有想過怎么樣創(chuàng)建一個自己的不可變的類呢?這篇文章就帶大家來實踐一下,創(chuàng)建一個自己的不可變的類2022-11-11