之前使用synchronized實(shí)現(xiàn)生產(chǎn)者與消費(fèi)者,雖然可行,也沒有錯(cuò)誤,但是最終喚醒全部線程的做法會犧牲程序的性能,造成無謂的浪費(fèi),在JDK1.5版本之前,對鎖的操作時(shí)隱式的,只能使用synchronized實(shí)現(xiàn)同步鎖的效果:
// 以synchronized代碼塊為例
synchronized (對象) {// 此時(shí)獲取鎖
// 要執(zhí)行的任務(wù)
} // 此時(shí)釋放鎖
之所以說是隱式,是因?yàn)槿绻麑Υ藘?nèi)容不夠了解的話,僅從以上代碼根本看不出鎖的體現(xiàn),但從JDK1.5版本開始,就可以對鎖進(jìn)行顯式的操作,增加了一個(gè)Lock接口,實(shí)際上是將鎖進(jìn)行了面向?qū)ο螅琇ock接口實(shí)現(xiàn)提供了比synchronized方法和語句可獲得的更廣泛的鎖定操作。
1、使用Lock修改示例代碼
此處僅使用Lock替代同步代碼塊,其余部分不變:
1.使用Lock接口子類ReentrantLock創(chuàng)建一把鎖
2.把之前寫在同步代碼塊中的內(nèi)容寫在lock()方法和unlock()方法之間
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//描述產(chǎn)品
class Product {
private String name;
private int count;
private boolean flag;
// 創(chuàng)建一把鎖
private Lock lock = new ReentrantLock();
// 生產(chǎn)產(chǎn)品的功能
public void produce(String name) {
lock.lock();// 獲取鎖
try{
while (flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = count + "個(gè)" + name;
System.out.println(Thread.currentThread().getName() + "生產(chǎn)了" + this.name);
count++;
flag = true;
notifyAll();
}
/*
* 為了保證線程正常運(yùn)行
* unlock()方法一定要執(zhí)行
* 因此將該方法放入finally塊中
* 但是finally塊不能單獨(dú)使用
* 因此使用try塊予以配合
*/
finally{
lock.unlock();// 釋放鎖
}
}
// 消費(fèi)產(chǎn)品的功能
public void consume() {
// 使用同一把鎖
lock.lock();
try{
while (!flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "消費(fèi)了" + this.name);
flag = false;
notifyAll();
}
finally{
lock.unlock();
}
}
}
此時(shí)結(jié)果如下:

結(jié)果很明顯,出現(xiàn)了IllegalMonitorStateException(無效的監(jiān)視器狀態(tài)異常),發(fā)生該異常的原因也很簡單,歸根到底就是wait()方法與notifyAll()方法。之前講過,這些方法一定要用在同步當(dāng)中,并且由對象,也就是鎖來調(diào)用,當(dāng)對象是this時(shí)可以省略不寫,而現(xiàn)在用Lock接口代替了synchronized,因此也就意味著沒有同步方法,自然也就無法使用這些方法了,那么如何解決這個(gè)問題呢?
2、使用Condition接口實(shí)現(xiàn)等待喚醒
JDK1.5版本對喚醒等待方法也進(jìn)行了面向?qū)ο螅碈ondition接口,該接口將Object類中的監(jiān)視器方法(wait()、notify()和 notifyAll())分解成截然不同的對象,以便通過將這些對象與任意Lock實(shí)現(xiàn)組合使用。其中,Lock替代了synchronized方法和語句的使用,Condition替代了 Object 監(jiān)視器方法的使用。Condition的實(shí)例實(shí)質(zhì)上被綁定到一個(gè)鎖上。要為特定Lock實(shí)例獲得Condition實(shí)例,可使用Lock接口中的newCondition()方法,示例代碼如下:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//描述產(chǎn)品
class Product {
private String name;
private int count;
private boolean flag;
private Lock lock = new ReentrantLock();
// 得到與鎖綁定的condition對象
private Condition con = lock.newCondition();
// 生產(chǎn)產(chǎn)品的功能
public void produce(String name) {
lock.lock();
try{
while (flag) {
try {
/*
* 雖然是用condition對象調(diào)用await()方法
* 但由于condition對象已經(jīng)于lock鎖綁定
* 因此實(shí)際上依然是讓持有特定鎖的線程進(jìn)入等待
*/
con.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = count + "個(gè)" + name;
System.out.println(Thread.currentThread().getName() + "生產(chǎn)了" + this.name);
count++;
flag = true;
// 同理用condition對象喚醒所有持有l(wèi)ock鎖的線程
con.signalAll();;
}
finally{
lock.unlock();
}
}
// 消費(fèi)產(chǎn)品的功能
public void consume() {
// 使用同一把鎖
lock.lock();
try{
while (!flag) {
try {
con.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "消費(fèi)了" + this.name);
flag = false;
con.signalAll();;
}
finally{
lock.unlock();
}
}
}
此時(shí)結(jié)果如下:

雖然目前使用Lock接口以及Condition接口實(shí)現(xiàn)了生產(chǎn)者與消費(fèi)者,但依然是喚醒全部線程,程序性能并沒有提升,此時(shí)就可以使用Lock接口與Condition接口的靈活性來避免這個(gè)問題。
3、JDK1.5版本后的多線程程序
在JDK1.5版本之前,只能使用synchronized方法實(shí)現(xiàn)同步,但synchronized方法的局限性是,wait()方法、notify()方法都必須放在synchronized代碼塊內(nèi)部,且操作鎖上線程的方法與鎖都是綁定在一起的,但是JDK1.5版本之后可以使用Lock接口直接創(chuàng)建一把鎖,而這把鎖可以使用newCondition()方法創(chuàng)建多個(gè)Condition對象,每一個(gè)對象都可以單獨(dú)實(shí)現(xiàn)await()、signa()等方法,但實(shí)際上這些Condition對象仍然使用的是同一把鎖,因此就實(shí)現(xiàn)了單獨(dú)控制線程而不需要統(tǒng)一喚醒,從而提升了程序的性能,示例代碼如下:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//描述產(chǎn)品
class Product {
private String name;
private int count;
private boolean flag;
private Lock lock = new ReentrantLock();
// 得到與鎖綁定的condition對象,控制生產(chǎn)線程的等待與喚醒
private Condition con1 = lock.newCondition();
// 得到與鎖綁定的condition對象,控制消費(fèi)線程的等待與喚醒
private Condition con2 = lock.newCondition();
// 生產(chǎn)產(chǎn)品的功能
public void produce(String name) {
lock.lock();
try{
while (flag) {
try {
// 生產(chǎn)線程con1進(jìn)入等待
con1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = count + "個(gè)" + name;
System.out.println(Thread.currentThread().getName() + "生產(chǎn)了" + this.name);
count++;
flag = true;
// 喚醒消費(fèi)線程con2
con2.signal();;
}
finally{
lock.unlock();
}
}
// 消費(fèi)產(chǎn)品的功能
public void consume() {
lock.lock();
try{
while (!flag) {
try {
// 消費(fèi)線程con2進(jìn)入等待
con2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "消費(fèi)了" + this.name);
flag = false;
// 喚醒生產(chǎn)線程con1
con1.signal();;
}
finally{
lock.unlock();
}
}
}
//生產(chǎn)任務(wù)
class Producer implements Runnable {
private Product pro;
public Producer(Product pro) {
this.pro = pro;
}
public void run() {
while (true) {
pro.produce("筆記本");
}
}
}
//消費(fèi)任務(wù)
class Consumer implements Runnable {
private Product pro;
public Consumer(Product pro) {
this.pro = pro;
}
public void run() {
while (true) {
pro.consume();
}
}
}
public class Demo1 {
public static void main(String[] args) {
Product pro = new Product();
Producer producer = new Producer(pro);
Consumer consumer = new Consumer(pro);
Thread t0 = new Thread(producer);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(consumer);
Thread t3 = new Thread(consumer);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
之所以con1和con2就可以控制生產(chǎn)線程與消費(fèi)線程,是因?yàn)榫€程T0、T1執(zhí)行的就是producer方法,在執(zhí)行該方法的con1.await();代碼時(shí),T0或T1線程就會等待,因此也就保證con1與生產(chǎn)線程實(shí)現(xiàn)了綁定,此時(shí)con2喚醒的只能是T2或T3線程,同理,消費(fèi)線程也是如此,實(shí)現(xiàn)了程序性能的提升。
4、使用Lock接口與Condition接口實(shí)現(xiàn)生產(chǎn)者與消費(fèi)者的完整示例代碼
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//描述產(chǎn)品
class Clothes {
// 產(chǎn)品名稱
private String name;
// 產(chǎn)品價(jià)格
private double price;
// 存放產(chǎn)品的容器
private Clothes[] arr = new Clothes[100];
// 創(chuàng)建生產(chǎn)使用的下標(biāo)
private int propointer;
// 創(chuàng)建消費(fèi)使用的下標(biāo)
private int conpointer;
// 創(chuàng)建一把鎖
private Lock lock = new ReentrantLock();
// 得到與鎖綁定的condition對象,控制生產(chǎn)線程的等待與喚醒
private Condition pro = lock.newCondition();
// 得到與鎖綁定的condition對象,控制消費(fèi)線程的等待與喚醒
private Condition con = lock.newCondition();
// 記錄產(chǎn)品數(shù)量
private int count;
// 生成無參的構(gòu)造方法
public Clothes() {
}
// 生成帶參的構(gòu)造方法
public Clothes(String name, double price) {
this.name = name;
this.price = price;
}
// 生產(chǎn)產(chǎn)品的功能
public void produce() {
lock.lock();
try {
/*
* 先判斷是否可以生成
* 當(dāng)容器滿時(shí)不生產(chǎn)
* 即產(chǎn)品數(shù)量與容器容量相同
*/
while (count == arr.length) {
try {
// 生產(chǎn)線程pro進(jìn)入等待
pro.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 將生產(chǎn)的衣服存入容器
arr[propointer] = new Clothes("襯衣", 9.15);
System.out.println(Thread.currentThread().getName() + "生產(chǎn)了" + arr[propointer] + ",此為第" + count + "件");
// 生產(chǎn)后數(shù)量加一
count++;
/*
* 判斷生產(chǎn)下標(biāo)
* 如果下標(biāo)加一之后與容量相同
* 說明容器已滿 下標(biāo)清零
*/
if (++propointer == arr.length) {
propointer = 0;
}
// 喚醒消費(fèi)線程con
con.signal();
;
} finally {
lock.unlock();
}
}
/*
* 因?yàn)檩敵鯽rr[propointer]
* 實(shí)際上Clothes類型的對象
* 輸出對象默認(rèn)調(diào)用其toString方法
* 因此還需要重寫該方法
*/
public String toString() {
return "價(jià)格為" + price + "英鎊的" + name;
}
// 消費(fèi)產(chǎn)品的功能
public void consume() {
lock.lock();
try {
/*
* 先判斷是否可以消費(fèi)
* 當(dāng)產(chǎn)品數(shù)量為0時(shí)
* 不能消費(fèi)
*/
while (count == 0) {
try {
// 消費(fèi)線程con進(jìn)入等待
con.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 消費(fèi)一件產(chǎn)品
Clothes clothes = arr[conpointer];
System.out.println(Thread.currentThread().getName() + "消費(fèi)了" + clothes);
// 產(chǎn)品總量減一
count--;
/*
* 判斷消費(fèi)下標(biāo)
* 如果下標(biāo)加一之后與容量相同
* 說明已取到最后一件產(chǎn)品
* 下標(biāo)清零
*/
if (++conpointer == arr.length) {
conpointer = 0;
}
// 喚醒生產(chǎn)線程pro
pro.signal();
;
} finally {
lock.unlock();
}
}
}
// 生產(chǎn)任務(wù)
class Producer implements Runnable {
private Clothes clo;
public Producer(Clothes clo) {
this.clo = clo;
}
public void run() {
while (true) {
clo.produce();
}
}
}
// 消費(fèi)任務(wù)
class Consumer implements Runnable {
private Clothes clo;
public Consumer(Clothes clo) {
this.clo = clo;
}
public void run() {
while (true) {
clo.consume();
}
}
}
public class Demo2 {
public static void main(String[] args) {
Clothes clo = new Clothes();
Producer producer = new Producer(clo);
Consumer consumer = new Consumer(clo);
Thread t0 = new Thread(producer);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(consumer);
Thread t3 = new Thread(consumer);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
此時(shí)結(jié)果如下:

版權(quán)聲明:歡迎轉(zhuǎn)載,歡迎擴(kuò)散,但轉(zhuǎn)載時(shí)請標(biāo)明作者以及原文出處,謝謝合作! ↓↓↓