生產(chǎn)者消費(fèi)者模型ThreadLocal原理及實(shí)例詳解
1、生產(chǎn)者消費(fèi)者模型作用和示例如下:
1)通過(guò)平衡生產(chǎn)者的生產(chǎn)能力和消費(fèi)者的消費(fèi)能力來(lái)提升整個(gè)系統(tǒng)的運(yùn)行效率 ,這是生產(chǎn)者消費(fèi)者模型最重要的作用
2)解耦,這是生產(chǎn)者消費(fèi)者模型附帶的作用,解耦意味著生產(chǎn)者和消費(fèi)者之間的聯(lián)系少,聯(lián)系越少越可以獨(dú)自發(fā)展而不需要收到相互的制約
備注:對(duì)于生產(chǎn)者消費(fèi)者模型的理解將在并發(fā)隊(duì)列BlockingQueue章節(jié)進(jìn)行說(shuō)明,本章不做詳細(xì)介紹。
package threadLearning.productCustomerModel; /* wait/notify 機(jī)制:以資源為例,生產(chǎn)者生產(chǎn)一個(gè)資源,通知消費(fèi)者就消費(fèi)掉一個(gè)資源,生產(chǎn)者繼續(xù)生產(chǎn)資源,消費(fèi)者消費(fèi)資源,以此循環(huán)。 wait():使一個(gè)線程處于等待(阻塞)狀態(tài),并且釋放所持有的對(duì)象的鎖; sleep(): 使一個(gè)正在運(yùn)行的線程處于睡眠狀態(tài), 是一個(gè)靜態(tài)方法, 調(diào)用此方法要處理 InterruptedException 異常; notify():?jiǎn)拘岩粋€(gè)處于等待狀態(tài)的線程,當(dāng)然在調(diào)用此方法的時(shí)候,并不能確切的喚醒某一個(gè)等待狀態(tài)的線程而是由 JVM 確定喚醒哪個(gè)線程,而且與優(yōu)先級(jí)無(wú)關(guān); notityAll():?jiǎn)拘阉刑幱诘却隣顟B(tài)的線程,該方法并不是將對(duì)象的鎖給所有線程,而是讓它們競(jìng)爭(zhēng),只有獲得鎖的線程才能進(jìn)入就緒狀態(tài); 備注:java 5 通過(guò) Lock 接口提供了顯示的鎖機(jī)制,Lock 接口中定義了加鎖(lock()方法)和解鎖(unLock()方法),增強(qiáng)了多線程編程的靈活性及對(duì)線程的協(xié)調(diào) */ //資源對(duì)象:包含商品名屬性;提供生產(chǎn)和消費(fèi)方法; class Resource { private String name;//商品名 private int count = 0; private boolean flag = false;//生產(chǎn)或者消費(fèi)的控制開(kāi)關(guān) public synchronized void set(String name) { // 生產(chǎn)資源 while (flag) { try { // 線程等待。消費(fèi)者消費(fèi)資源 wait(); } catch (Exception e) { } } this.name = name + "---" + count++; System.out.println(Thread.currentThread().getName() + "...生產(chǎn)者..." + this.name); flag = true; // 喚醒等待中的消費(fèi)者 this.notifyAll();//喚醒在此對(duì)象監(jiān)視器上等待的所有線程 Object.notifyAll() } public synchronized void out() { // 消費(fèi)資源 while (!flag) { // 線程等待,生產(chǎn)者生產(chǎn)資源 try { wait(); } catch (Exception e) { } } System.out.println(Thread.currentThread().getName() + "...消費(fèi)者..." + this.name); flag = false; // 喚醒生產(chǎn)者,生產(chǎn)資源 this.notifyAll(); } } // 生產(chǎn)者 class Producer implements Runnable { private Resource res; Producer(Resource res) { this.res = res; } // 生產(chǎn)者生產(chǎn)資源 public void run() { while (true) { res.set("商品"); } } } // 消費(fèi)者消費(fèi)資源 class Consumer implements Runnable { private Resource res; Consumer(Resource res) { this.res = res; } public void run() { while (true) { res.out(); } } } public class ProducerConsumerDemo { public static void main(String[] args) { Resource r = new Resource(); Producer pro = new Producer(r); Consumer con = new Consumer(r); Thread t1 = new Thread(pro); Thread t2 = new Thread(con); t1.start(); t2.start(); } }
2、ThreadLocal
ThreadLocal提供一個(gè)線程的局部變量,訪問(wèn)某個(gè)線程擁有自己局部變量。當(dāng)使用ThreadLocal維護(hù)變量時(shí),ThreadLocal為每個(gè)使用該變量的線程提供獨(dú)立的變量副本,所以每一個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)影響其它線程所對(duì)應(yīng)的副本。
ThreadLocal的接口方法只有4個(gè)方法,先來(lái)了解一下:
•void set(Object value)設(shè)置當(dāng)前線程的線程局部變量的值;
•public Object get()該方法返回當(dāng)前線程所對(duì)應(yīng)的線程局部變量;
•public void remove()將當(dāng)前線程局部變量的值刪除,目的是為了減少內(nèi)存的占用,該方法是JDK 5.0新增的方法。需要指出的是,
當(dāng)線程結(jié)束后,對(duì)應(yīng)該線程的局部變量將自動(dòng)被垃圾回收,所以顯式調(diào)用該方法清除線程的局部變量并不是必須的操作,但它可以加快內(nèi)存回收的速度;
•protected Object initialValue()返回該線程局部變量的初始值,該方法是一個(gè)protected的方法,顯然是為了讓子類覆蓋而設(shè)計(jì)的。
這個(gè)方法是一個(gè)延遲調(diào)用方法,在線程第1次調(diào)用get()或set(Object)時(shí)才執(zhí)行,并且僅執(zhí)行1次。ThreadLocal中的缺省實(shí)現(xiàn)直接返回一個(gè)null;
總的來(lái)說(shuō)ThreadLocal就是一種以 空間換時(shí)間 的做法,在每個(gè)Thread里面維護(hù)了一個(gè)以開(kāi)地址法實(shí)現(xiàn)的ThreadLocal.ThreadLocalMap,把數(shù)據(jù)進(jìn)行隔離,數(shù)據(jù)不共享,自然就沒(méi)有線程安全方面的問(wèn)題了。
示例1:
package threadLearning.thredLocal; /* 1、該類提供了線程局部 (thread-local) 變量。這些變量不同于它們的普通對(duì)應(yīng)物,因?yàn)樵L問(wèn)某個(gè)變量(通過(guò)其 get 或 set 方法)的每個(gè)線程都有自己的局部變量,它獨(dú)立于變量的初始化副本。ThreadLocal 實(shí)例通常是類中的 private static 字段,它們希望將狀態(tài)與某一個(gè)線程(例如:用戶 ID 或事務(wù) ID)相關(guān)聯(lián)。 2、ThreadLocal的使用 (1) 在關(guān)聯(lián)數(shù)據(jù)類中創(chuàng)建 private static ThreadLocal在下面的類中,私有靜態(tài) ThreadLocal 實(shí)例(serialNum)為調(diào)用該類的靜態(tài) SerialNum.get() 方法的每個(gè)線程維護(hù)了一個(gè)“序列號(hào)”,該方法將返回當(dāng)前線程的序列號(hào)。(線程的序列號(hào)是在第一次調(diào)用 SerialNum.get() 時(shí)分配的,并在后續(xù)調(diào)用中不會(huì)更改。 每個(gè)線程都保持對(duì)其線程局部變量副本的隱式引用,只要線程是活動(dòng)的并且 ThreadLocal 實(shí)例是可訪問(wèn)的;在線程消失之后,其線程局部實(shí)例的所有副本都會(huì)被垃圾回收(除非存在對(duì)這些副本的其他引用)。 */ public class SerialNum { private static int nextSerialNum = 3; private static ThreadLocal serialNum = new ThreadLocal() {//創(chuàng)建一個(gè)線程本地變量 protected synchronized Object initialValue() { return new Integer(nextSerialNum++); } }; public static int get() { return ((Integer) (serialNum.get())).intValue(); } public static void main(String args[]){ Thread thead1=new Thread(new Runnable() { public void run() { System.out.println("thead1-->"+get()); } }); Thread thead2=new Thread(new Runnable() { public void run() { System.out.println("thead2-->"+get()); } }); thead1.start(); thead2.start(); /* 同一個(gè)Thread啟動(dòng)第二次會(huì)報(bào)錯(cuò)java.lang.IllegalThreadStateExceptionThread報(bào)錯(cuò)的原因,并不是說(shuō),重新啟動(dòng)Thread導(dǎo)致的, 而是因?yàn)楣灿靡粋€(gè)Thread導(dǎo)致的,因?yàn)?,如果是?shí)現(xiàn)Runnable的類,每次啟動(dòng)線程都需要new Thread(Runnable).start(),這就使得線 程沒(méi)有被共用。 while(true){ thead2.start(); } */ } }
運(yùn)行結(jié)果:
thead1-->3
thead2-->4
示例2:
package threadLearning.thredLocal; class Res { // 生成序列號(hào)共享變量 public static Integer count = 0; public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() { // 覆蓋返回此線程局部變量的當(dāng)前線程的“初始值”方法 @Override protected Integer initialValue() { return 0; }; }; public Integer getNum() { int count = threadLocal.get() + 1;//get() 該方法返回當(dāng)前線程所對(duì)應(yīng)的線程局部變量 threadLocal.set(count);//將此線程局部變量的當(dāng)前線程副本中的值設(shè)置為指定值 return count; } } public class ThreadLocaDemo2 extends Thread { private Res res; public ThreadLocaDemo2(Res res) { this.res = res; } @Override public void run() { for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName() + "---" + "i---" + i + "--num:" + res.getNum()); } } public static void main(String[] args) { Res res = new Res(); ThreadLocaDemo2 threadLocaDemo1 = new ThreadLocaDemo2(res); ThreadLocaDemo2 threadLocaDemo2 = new ThreadLocaDemo2(res); ThreadLocaDemo2 threadLocaDemo3 = new ThreadLocaDemo2(res); threadLocaDemo1.start(); threadLocaDemo2.start(); threadLocaDemo3.start(); } }
運(yùn)行結(jié)果:
Thread-1---i---0--num:1 Thread-2---i---0--num:1 Thread-0---i---0--num:1 Thread-2---i---1--num:2 Thread-1---i---1--num:2 Thread-2---i---2--num:3 Thread-0---i---1--num:2 Thread-1---i---2--num:3 Thread-0---i---2--num:3
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Java單線程ThreadLocal串值問(wèn)題解決方案
- java中ThreadLocalRandom的使用詳解
- Python threading.local代碼實(shí)例及原理解析
- JAVA開(kāi)發(fā)常用類庫(kù)UUID、Optional、ThreadLocal、TimerTask、Base64使用方法與實(shí)例詳解
- python語(yǔ)言線程標(biāo)準(zhǔn)庫(kù)threading.local解讀總結(jié)
- ThreadLocal原理及內(nèi)存泄漏原因
- Java ThreadLocal類應(yīng)用實(shí)戰(zhàn)案例分析
- Java ThreadLocal用法實(shí)例詳解
相關(guān)文章
Spring源碼剖析之Spring處理循環(huán)依賴的問(wèn)題
大家都知道循環(huán)依賴依賴指的是Bean與Bean之間的依賴關(guān)系,循環(huán)依賴指的是兩個(gè)或者多個(gè)Bean相互依賴,本文通過(guò)代碼示例給大家講解Spring處理循環(huán)依賴的問(wèn)題,感興趣的朋友一起看看吧2021-06-06java正則匹配讀取txt文件提取特定開(kāi)頭和結(jié)尾的字符串
通常我們可以直接通過(guò)文件流來(lái)讀取txt文件的內(nèi)容,但有時(shí)候也會(huì)遇到問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于java正則匹配讀取txt文件提取特定開(kāi)頭和結(jié)尾的字符串的相關(guān)資料,需要的朋友可以參考下2022-11-11淺談Java后臺(tái)對(duì)JSON格式的處理操作
下面小編就為大家?guī)?lái)一篇淺談Java后臺(tái)對(duì)JSON格式的處理操作。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-06-06詳解Java如何通過(guò)裝飾器模式擴(kuò)展系統(tǒng)功能
這篇文章主要為大家詳細(xì)介紹了Java如何通過(guò)裝飾器模式擴(kuò)展系統(tǒng)功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-04-04WebUploader實(shí)現(xiàn)圖片上傳功能
這篇文章主要為大家詳細(xì)介紹了WebUploader實(shí)現(xiàn)圖片上傳功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03java中throws與try...catch的區(qū)別點(diǎn)
在本篇文章里小編給大家整理了一篇關(guān)于java中throws與try...catch的區(qū)別點(diǎn)的內(nèi)容,需要的朋友們跟著學(xué)習(xí)下。2020-02-02java使用websocket,并且獲取HttpSession 源碼分析(推薦)
這篇文章主要介紹了java使用websocket,并且獲取HttpSession,通過(guò)使用配置源碼分析了各方面知識(shí)點(diǎn),具體操作步驟大家可查看下文的詳細(xì)講解,感興趣的小伙伴們可以參考一下。2017-08-08淺談Java中隨機(jī)數(shù)的幾種實(shí)現(xiàn)方式
這篇文章主要介紹了Java中隨機(jī)數(shù)的幾種實(shí)現(xiàn)方式,從最簡(jiǎn)單的Math.random到多線程的并發(fā)實(shí)現(xiàn)都在本文所列之中,需要的朋友可以參考下2015-07-07