生產(chǎn)者消費者模型ThreadLocal原理及實例詳解
1、生產(chǎn)者消費者模型作用和示例如下:
1)通過平衡生產(chǎn)者的生產(chǎn)能力和消費者的消費能力來提升整個系統(tǒng)的運行效率 ,這是生產(chǎn)者消費者模型最重要的作用
2)解耦,這是生產(chǎn)者消費者模型附帶的作用,解耦意味著生產(chǎn)者和消費者之間的聯(lián)系少,聯(lián)系越少越可以獨自發(fā)展而不需要收到相互的制約
備注:對于生產(chǎn)者消費者模型的理解將在并發(fā)隊列BlockingQueue章節(jié)進行說明,本章不做詳細介紹。
package threadLearning.productCustomerModel;
/*
wait/notify 機制:以資源為例,生產(chǎn)者生產(chǎn)一個資源,通知消費者就消費掉一個資源,生產(chǎn)者繼續(xù)生產(chǎn)資源,消費者消費資源,以此循環(huán)。
wait():使一個線程處于等待(阻塞)狀態(tài),并且釋放所持有的對象的鎖;
sleep(): 使一個正在運行的線程處于睡眠狀態(tài), 是一個靜態(tài)方法, 調(diào)用此方法要處理 InterruptedException 異常;
notify():喚醒一個處于等待狀態(tài)的線程,當然在調(diào)用此方法的時候,并不能確切的喚醒某一個等待狀態(tài)的線程而是由 JVM 確定喚醒哪個線程,而且與優(yōu)先級無關(guān);
notityAll():喚醒所有處于等待狀態(tài)的線程,該方法并不是將對象的鎖給所有線程,而是讓它們競爭,只有獲得鎖的線程才能進入就緒狀態(tài);
備注:java 5 通過 Lock 接口提供了顯示的鎖機制,Lock 接口中定義了加鎖(lock()方法)和解鎖(unLock()方法),增強了多線程編程的靈活性及對線程的協(xié)調(diào)
*/
//資源對象:包含商品名屬性;提供生產(chǎn)和消費方法;
class Resource {
private String name;//商品名
private int count = 0;
private boolean flag = false;//生產(chǎn)或者消費的控制開關(guān)
public synchronized void set(String name) {
// 生產(chǎn)資源
while (flag) {
try {
// 線程等待。消費者消費資源
wait();
} catch (Exception e) {
}
}
this.name = name + "---" + count++;
System.out.println(Thread.currentThread().getName() + "...生產(chǎn)者..."
+ this.name);
flag = true;
// 喚醒等待中的消費者
this.notifyAll();//喚醒在此對象監(jiān)視器上等待的所有線程 Object.notifyAll()
}
public synchronized void out() {
// 消費資源
while (!flag) {
// 線程等待,生產(chǎn)者生產(chǎn)資源
try {
wait();
} catch (Exception e) {
}
}
System.out.println(Thread.currentThread().getName() + "...消費者..."
+ 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("商品");
}
}
}
// 消費者消費資源
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提供一個線程的局部變量,訪問某個線程擁有自己局部變量。當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應(yīng)的副本。
ThreadLocal的接口方法只有4個方法,先來了解一下:
•void set(Object value)設(shè)置當前線程的線程局部變量的值;
•public Object get()該方法返回當前線程所對應(yīng)的線程局部變量;
•public void remove()將當前線程局部變量的值刪除,目的是為了減少內(nèi)存的占用,該方法是JDK 5.0新增的方法。需要指出的是,
當線程結(jié)束后,對應(yīng)該線程的局部變量將自動被垃圾回收,所以顯式調(diào)用該方法清除線程的局部變量并不是必須的操作,但它可以加快內(nèi)存回收的速度;
•protected Object initialValue()返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是為了讓子類覆蓋而設(shè)計的。
這個方法是一個延遲調(diào)用方法,在線程第1次調(diào)用get()或set(Object)時才執(zhí)行,并且僅執(zhí)行1次。ThreadLocal中的缺省實現(xiàn)直接返回一個null;
總的來說ThreadLocal就是一種以 空間換時間 的做法,在每個Thread里面維護了一個以開地址法實現(xiàn)的ThreadLocal.ThreadLocalMap,把數(shù)據(jù)進行隔離,數(shù)據(jù)不共享,自然就沒有線程安全方面的問題了。
示例1:
package threadLearning.thredLocal;
/*
1、該類提供了線程局部 (thread-local) 變量。這些變量不同于它們的普通對應(yīng)物,因為訪問某個變量(通過其 get 或 set 方法)的每個線程都有自己的局部變量,它獨立于變量的初始化副本。ThreadLocal 實例通常是類中的 private static 字段,它們希望將狀態(tài)與某一個線程(例如:用戶 ID 或事務(wù) ID)相關(guān)聯(lián)。
2、ThreadLocal的使用
(1) 在關(guān)聯(lián)數(shù)據(jù)類中創(chuàng)建 private static ThreadLocal在下面的類中,私有靜態(tài) ThreadLocal 實例(serialNum)為調(diào)用該類的靜態(tài) SerialNum.get() 方法的每個線程維護了一個“序列號”,該方法將返回當前線程的序列號。(線程的序列號是在第一次調(diào)用 SerialNum.get() 時分配的,并在后續(xù)調(diào)用中不會更改。
每個線程都保持對其線程局部變量副本的隱式引用,只要線程是活動的并且 ThreadLocal 實例是可訪問的;在線程消失之后,其線程局部實例的所有副本都會被垃圾回收(除非存在對這些副本的其他引用)。
*/
public class SerialNum {
private static int nextSerialNum = 3;
private static ThreadLocal serialNum = new ThreadLocal() {//創(chuàng)建一個線程本地變量
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();
/*
同一個Thread啟動第二次會報錯java.lang.IllegalThreadStateExceptionThread報錯的原因,并不是說,重新啟動Thread導致的,
而是因為共用一個Thread導致的,因為,如果是實現(xiàn)Runnable的類,每次啟動線程都需要new Thread(Runnable).start(),這就使得線
程沒有被共用。
while(true){
thead2.start();
}
*/
}
}
運行結(jié)果:
thead1-->3
thead2-->4
示例2:
package threadLearning.thredLocal;
class Res {
// 生成序列號共享變量
public static Integer count = 0;
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
// 覆蓋返回此線程局部變量的當前線程的“初始值”方法
@Override
protected Integer initialValue() {
return 0;
};
};
public Integer getNum() {
int count = threadLocal.get() + 1;//get() 該方法返回當前線程所對應(yīng)的線程局部變量
threadLocal.set(count);//將此線程局部變量的當前線程副本中的值設(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();
}
}
運行結(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
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
java獲取新insert數(shù)據(jù)自增id的實現(xiàn)方法
這篇文章主要介紹了java獲取新insert數(shù)據(jù)自增id的實現(xiàn)方法,在具體生成id的時候,我們的操作順序一般是:先在主表中插入記錄,然后獲得自動生成的id,以它為基礎(chǔ)插入從表的記錄,需要的朋友可以參考下2019-06-06
MybatisPlus插件自動維護更新和創(chuàng)建時間方式
這篇文章主要介紹了MybatisPlus插件自動維護更新和創(chuàng)建時間方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-04-04
Java實現(xiàn)word文檔轉(zhuǎn)成圖片的示例詳解
本文主要為大家詳細介紹了如何在Java項目中引用aspose-words和poi-tljar包實現(xiàn)word文檔轉(zhuǎn)成圖片,感興趣的小伙伴可以跟隨小編一起學習一下2024-10-10
JavaWeb中請求轉(zhuǎn)發(fā)和請求重定向的區(qū)別以及使用
今天帶大家學習JavaWeb的相關(guān)知識,文章圍繞著JavaWeb中請求轉(zhuǎn)發(fā)和請求重定向的區(qū)別以及使用展開,文中有非常詳細的介紹,需要的朋友可以參考下2021-06-06

