Java阻塞隊(duì)列的實(shí)現(xiàn)及應(yīng)用
1.手寫生產(chǎn)者消費(fèi)者模型
所謂生產(chǎn)者消費(fèi)者模型,可以用我們生活中的例子來類比:我去一個(gè)小攤兒買吃的,老板把已經(jīng)做好的小吃都放在擺盤上,供我挑選。那么,老板就是生產(chǎn)者;我就是消費(fèi)者;擺盤就是阻塞隊(duì)列,用來當(dāng)做生產(chǎn)與消費(fèi)的緩沖區(qū)。因此,阻塞隊(duì)列在生產(chǎn)者與消費(fèi)者模型中起著至關(guān)重要的緩沖作用。
此次先演示如何手寫阻塞隊(duì)列(也可以使用Java庫中自帶的阻塞隊(duì)列)。
手寫的阻塞隊(duì)列只實(shí)現(xiàn)最基礎(chǔ)的兩個(gè)功能:入隊(duì)和出隊(duì)。之所以叫阻塞隊(duì)列,是因?yàn)楫?dāng)隊(duì)空或者隊(duì)滿的時(shí)候,都要實(shí)現(xiàn)阻塞,直到隊(duì)中不空或不滿的時(shí)候,才會(huì)取消阻塞。
手寫阻塞隊(duì)列實(shí)現(xiàn)如下:
//阻塞隊(duì)列BlockQueue static class BlockQueue{ //該隊(duì)列用一個(gè)數(shù)組來實(shí)現(xiàn),我們讓此隊(duì)列的最大容量為10 private int[] items = new int[10]; private int head = 0; private int tail = 0; private int size = 0; private Object locker =new Object(); //入隊(duì) public void put(int item) throws InterruptedException { synchronized(locker) { while (size == items.length) { //入隊(duì)時(shí),若隊(duì)滿,阻塞 locker.wait(); } items[tail++] = item; //如果到達(dá)末尾,重回隊(duì)首(實(shí)現(xiàn)循環(huán)隊(duì)列) if (tail >= items.length) { tail = 0; } size++; locker.notify(); } } //出隊(duì) public int back() throws InterruptedException { int ret = 0; synchronized (locker) { while (size == 0) { //出隊(duì)時(shí),若隊(duì)空,阻塞 locker.wait(); } ret = items[head++]; if (head >= items.length) { head = 0; } size--; locker.notify(); } return ret; } }
用兩個(gè)線程充當(dāng)生產(chǎn)者與消費(fèi)者:
public static void main(String[] args) throws InterruptedException { BlockQueue blockQueue = new BlockQueue(); //生產(chǎn)者線程 Thread produce = new Thread(){ @Override public void run() { for(int i = 0;i<10000;++i){ try { System.out.println("生產(chǎn)了:"+i); blockQueue.put(i); } catch (InterruptedException e) { e.printStackTrace(); } } } }; produce.start(); //消費(fèi)者線程 Thread customer = new Thread(){ @Override public void run() { while (true) { try { int res = blockQueue.back(); System.out.println("消費(fèi)了:" + res); //每次消費(fèi)后等1秒,也就是生產(chǎn)的快,消費(fèi)的慢 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; customer.start(); customer.join(); produce.join(); }
結(jié)果如下:可以看到,生產(chǎn)者線程先生產(chǎn)元素,(阻塞隊(duì)列容量為10),當(dāng)隊(duì)列滿時(shí),隊(duì)列阻塞,消費(fèi)者線程消費(fèi)元素,因?yàn)橄M(fèi)的慢,所以接下來生產(chǎn)者線程由于阻塞隊(duì)列不能快速生產(chǎn),只能等待消費(fèi)者線程消費(fèi)隊(duì)列中的元素,生產(chǎn)者線程才能隨著生產(chǎn),這就是阻塞隊(duì)列的緩沖作用。
2.手寫定時(shí)器
先看一下Java包中的定時(shí)器。
下面的代碼我們通過調(diào)用timer類中的schedule方法來實(shí)現(xiàn)定時(shí)器功能。schedule方法有兩個(gè)參數(shù),第一個(gè)參數(shù):要執(zhí)行的任務(wù),第二個(gè)參數(shù):時(shí)間。
下面的代碼中,schedule方法中的第一個(gè)任務(wù)參數(shù):我們創(chuàng)建了一個(gè)TimerTask實(shí)例;重寫里面的run方法來打印"觸發(fā)定時(shí)器"這句話。第二個(gè)參數(shù):3000;表示3秒后執(zhí)行這個(gè)任務(wù)。
import java.util.Timer; import java.util.TimerTask; public class Test{ public static void main(String[] args) { Timer timer = new Timer(); System.out.println("代碼開始執(zhí)行"); timer.schedule(new TimerTask() { @Override public void run() { System.out.println("觸發(fā)定時(shí)器"); } },3000); } }
結(jié)果如下:
從上面就可以看出來我們手寫定時(shí)器需要實(shí)現(xiàn)以下兩個(gè)方面:
1.一個(gè)Task類,用來描述要實(shí)現(xiàn)的任務(wù)
2.一個(gè)Timer類,類中再實(shí)現(xiàn)一個(gè)schedule方法
Task類實(shí)現(xiàn)
//Task類用來描述任務(wù),它繼承Comparable接口是因?yàn)橐獙⑷蝿?wù)放到優(yōu)先級阻塞隊(duì)列中 static class Task implements Comparable<Task>{ //command表示這個(gè)任務(wù)是什么 private Runnable command; //time是一個(gè)時(shí)間戳 private long time; public Task(Runnable command,long time){ this.command = command; this.time = System.currentTimeMillis()+time; } public void run(){ command.run(); } //因?yàn)橐獙ask任務(wù)放到優(yōu)先級阻塞隊(duì)列中,所以要重寫compareTo方法,我們將時(shí)間短的任務(wù)放到隊(duì)頭 @Override public int compareTo(Task o) { return (int)(this.time - o.time); } }
Timer類實(shí)現(xiàn)
//Timer類中需要有一個(gè)定時(shí)器,還需要有一個(gè)schedule方法 static class Timer{ //使用優(yōu)先級阻塞隊(duì)列來放這些任務(wù),這樣才能把最接近時(shí)鐘的任務(wù)放到隊(duì)頭,我們每次掃描隊(duì)頭任務(wù)就行了 private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>(); //locker用來解決忙等問題 private Object locker = new Object(); //構(gòu)造方法中完成定時(shí)器功能 public Timer(){ //需要構(gòu)造一個(gè)線程,來不斷地掃描隊(duì)頭,來判斷隊(duì)頭任務(wù)是否到點(diǎn),也就是是否該開始執(zhí)行了 Thread t = new Thread(){ @Override public void run() { while(true){ //取出隊(duì)首任務(wù)來判斷是否到時(shí)間了 try { Task task = queue.take(); long current = System.currentTimeMillis(); //當(dāng)前時(shí)間戳小于時(shí)鐘時(shí)間戳,表明時(shí)間還沒到,那就等待 if (current < task.time){ queue.put(task); synchronized (locker){ locker.wait(task.time-current); } }else{ //否則時(shí)間到,開始執(zhí)行任務(wù) task.run(); } } catch (InterruptedException e) { e.printStackTrace(); break; } } } }; t.start(); } //schedule方法的兩個(gè)參數(shù),command為任務(wù),delay為一個(gè)時(shí)間差例如:3000(單位為毫秒) public void schedule(Runnable command,long delay){ Task task = new Task(command,delay); queue.put(task); synchronized (locker){ locker.notify(); } } }
主線程
public static void main(String[] args) { System.out.println("程序啟動(dòng)"); Timer timer = new Timer(); timer.schedule(new Runnable() { @Override public void run() { System.out.println("觸發(fā)定時(shí)器"); } },3000);//3000表示定時(shí)時(shí)間為3秒 }
結(jié)果如下:“程序啟動(dòng)” 在程序啟動(dòng)是立刻顯示出來;“觸發(fā)定時(shí)器”在3秒后顯示出來。
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
使用React和springboot做前后端分離項(xiàng)目的步驟方式
這篇文章主要介紹了使用React和springboot做前后端分離項(xiàng)目的步驟方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08SpringBoot整合RabbitMQ的5種模式的注解綁定詳解
這篇文章主要介紹了SpringBoot整合RabbitMQ的5種模式的注解綁定詳解,RabbitMQ 是一個(gè)消息中間件,它接收消息并且轉(zhuǎn)發(fā),是"消費(fèi)-生產(chǎn)者模型"的一個(gè)典型的代表,一端往消息隊(duì)列中不斷的寫入消息,而另一端則可以讀取或者訂閱隊(duì)列中的消息,需要的朋友可以參考下2024-01-01IDEA創(chuàng)建Maven項(xiàng)目一直顯示正在加載的問題及解決
這篇文章主要介紹了IDEA創(chuàng)建Maven項(xiàng)目一直顯示正在加載的問題及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12Java使用Collections工具類對List集合進(jìn)行排序
這篇文章主要介紹了Java使用Collections工具類對List集合進(jìn)行排序,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10