線程安全的類
StringBuffer
- 線程安全,可變的字符序列
- 從版本JDK5開始,被StringBuilder替代。通常應(yīng)該使用StringBuilder類,因為它支持所有相同的操作,但它更快,因為它不執(zhí)行同步
Vector
- 從Java2平臺v1.2開始,該類改進(jìn)了List接口,使其成為Java Collections Framework的成員。與新的集合實現(xiàn)不同,Vector被同步。如果不需要線程安全的實現(xiàn),建議使用ArrayList代替Vector
Hashtable:
- 該類實現(xiàn)了一個哈希表,它將鍵映射到值。任何非null對象都可以用作鍵或者值
- 從Java 2平臺v1.2 開始,該類進(jìn)行了改進(jìn),實現(xiàn)了Map接口,使其成為Java Collections Framework的成員。與新的集合實現(xiàn)不同,Hashtable被同步。如果不需要線程安全的實現(xiàn),,建議使用HashMap代替Hashtable
Lock鎖
為了更清晰的表達(dá)如何加鎖和釋放鎖,JDK5以后提供了一個新的鎖對象Lock
- void lock():獲得鎖
- void unlock():釋放鎖
Lock是接口不能直接實例化,這里采用它的實現(xiàn)類ReentrantLock來實例化
ReentrantLock的構(gòu)造方法 - ReentrantLock():創(chuàng)建一個ReentrantLock的實例
public class SellTicket implements Runnable {
private int tickets = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();
if(tickets>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"張票");
tickets--;
}
} finally {
lock.unlock();
}
}
}
}
生產(chǎn)者消費者模式概述
生產(chǎn)者消費者模式是一個十分經(jīng)典的多線程協(xié)作的模式,弄懂生產(chǎn)者消費者問題能夠讓我們對多線程編程的理解更加深刻所謂生產(chǎn)者消費者問題,實際上主要是包含了兩類線程:
- 一類是生產(chǎn)者線程用于生產(chǎn)數(shù)據(jù)
- 一類是消費者線程用于消費數(shù)據(jù)
為了解耦生產(chǎn)者和消費者的關(guān)系,通常會采用共享數(shù)據(jù)區(qū)域,就像是一個倉庫
- 生產(chǎn)者生產(chǎn)數(shù)據(jù)之后直接放置在共享數(shù)據(jù)區(qū)中,并不需要關(guān)心消費者的行為
- 消費者只需要從共享數(shù)據(jù)區(qū)中去獲取數(shù)據(jù),并不需要關(guān)心生產(chǎn)者的行為
為了體現(xiàn)生產(chǎn)和消費過程中的等待和喚醒,Java就提供了幾個方法供我們使用,這幾個方法在Object類中
Object類的等待和喚醒方法:
| 方法名 | 說明 |
|---|---|
| void wait() | 導(dǎo)致當(dāng)前線程等待,直到另一個線程調(diào)用該對象的notify()方法或notifyAll()方法 |
| void notify() | 喚醒正在等待對象監(jiān)視器的單個線程 |
| void notifyAll() | 喚醒正在等待對象監(jiān)視器的所有線程 |
生產(chǎn)者消費者案例
生產(chǎn)者消費者案例中包含的類:
- 奶箱類(Box):定義一個成員變量,表示第x瓶奶,提供存儲牛奶和獲取牛奶的操作
- 生產(chǎn)者類(Producer):實現(xiàn)Runnable接口,重寫run()方法,調(diào)用存儲牛奶的操作
- 消費者類(Customer):實現(xiàn)Runnable接口,重寫run()方法,調(diào)用獲取牛奶的操作
- 測試類(BoxDemo):里面有main方法,main方法中的代碼步如下:
- 創(chuàng)建奶箱對象,這是共享數(shù)據(jù)區(qū)域
- 創(chuàng)建生產(chǎn)者對象,把奶箱對象作為構(gòu)造方法參數(shù)傳遞,因為在這個類中要調(diào)用存儲牛奶的操作
- 創(chuàng)建消費者對象,把奶箱對象作為構(gòu)造方法參數(shù)傳遞,因為在這個類中要調(diào)用獲取牛奶的操作
- 創(chuàng)建兩個線程對象,分別把生產(chǎn)者對象和消費者對象作為構(gòu)造方法參數(shù)傳遞
- 啟動線程
生產(chǎn)者
public class Producer implements Runnable {
private Box b;
public Producer(Box b) {
this.b = b;
}
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
b.put(i);
}
}
}
消費者
public class Customer implements Runnable{
private Box b; // 定義一個成員變量
public Customer(Box b) {
this.b = b;
}
@Override
public void run() {
while (true) {
b.get();
}
}
}
奶箱類
public class Box {
// 定義一個成員變量,表示第x瓶奶
private int milk;
// 定義一個成員變量,表示奶箱的狀態(tài)
private boolean state = false;
public synchronized void put(int milk) {
// 如果有牛奶,等待消費
if(state) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果沒有牛奶,就生產(chǎn)牛奶
this.milk = milk;
System.out.println("送奶工將第" + this.milk + "瓶奶放入奶箱");
// 生產(chǎn)完畢之后,修改奶箱狀態(tài)
state = true;
// 喚醒其他等待的線程
notifyAll();
}
public synchronized void get() {
// 如果沒有牛奶,等待生產(chǎn)
if(!state){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果有牛奶,就消費牛奶
System.out.println("用戶拿到第"+this.milk +"瓶奶") ;
// 消費完畢之后,修改奶箱狀態(tài)
state = false;
// 喚醒其他等待的線程
notifyAll();
}
}
測試類
public class BoxDemo {
public static void main(String[] args) {
// 創(chuàng)建奶箱對象,這是共享數(shù)據(jù)區(qū)域
Box b = new Box();
// 創(chuàng)建生產(chǎn)者對象,把奶箱對象作為構(gòu)造方法參數(shù)傳遞,因為在這個類中要調(diào)用存儲牛奶的操作
Producer p = new Producer(b);
// 創(chuàng)建消費者對象,把奶箱對象作為構(gòu)造方法參數(shù)傳遞,因為在這個類中要調(diào)用獲取牛奶的操作
Customer c = new Customer(b);
// 創(chuàng)建兩個線程對象,分別把生產(chǎn)者對象和消費者對象作為構(gòu)造方法參數(shù)傳遞
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
//啟動線程
t1.start();
t2.start();
}
}

代碼運行截圖.png