之前使用synchronized實現(xiàn)生產(chǎn)者與消費者初烘,雖然可行罢缸,也沒有錯誤肤京,但是最終喚醒全部線程的做法會犧牲程序的性能颊艳,造成無謂的浪費,在JDK1.5版本之前,對鎖的操作時隱式的棋枕,只能使用synchronized實現(xiàn)同步鎖的效果:
// 以synchronized代碼塊為例
synchronized (對象) {// 此時獲取鎖
// 要執(zhí)行的任務(wù)
} // 此時釋放鎖
之所以說是隱式白修,是因為如果對此內(nèi)容不夠了解的話,僅從以上代碼根本看不出鎖的體現(xiàn)戒悠,但從JDK1.5版本開始熬荆,就可以對鎖進行顯式的操作,增加了一個Lock接口绸狐,實際上是將鎖進行了面向?qū)ο舐笨遥琇ock接口實現(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 + "個" + name;
System.out.println(Thread.currentThread().getName() + "生產(chǎn)了" + this.name);
count++;
flag = true;
notifyAll();
}
/*
* 為了保證線程正常運行
* unlock()方法一定要執(zhí)行
* 因此將該方法放入finally塊中
* 但是finally塊不能單獨使用
* 因此使用try塊予以配合
*/
finally{
lock.unlock();// 釋放鎖
}
}
// 消費產(chǎn)品的功能
public void consume() {
// 使用同一把鎖
lock.lock();
try{
while (!flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "消費了" + this.name);
flag = false;
notifyAll();
}
finally{
lock.unlock();
}
}
}
此時結(jié)果如下:
結(jié)果很明顯,出現(xiàn)了IllegalMonitorStateException(無效的監(jiān)視器狀態(tài)異常)符相,發(fā)生該異常的原因也很簡單拆融,歸根到底就是wait()方法與notifyAll()方法。之前講過啊终,這些方法一定要用在同步當(dāng)中镜豹,并且由對象,也就是鎖來調(diào)用蓝牲,當(dāng)對象是this時可以省略不寫趟脂,而現(xiàn)在用Lock接口代替了synchronized,因此也就意味著沒有同步方法例衍,自然也就無法使用這些方法了昔期,那么如何解決這個問題呢?
2佛玄、使用Condition接口實現(xiàn)等待喚醒
JDK1.5版本對喚醒等待方法也進行了面向?qū)ο笈鹨唬碈ondition接口,該接口將Object類中的監(jiān)視器方法(wait()梦抢、notify()和 notifyAll())分解成截然不同的對象般贼,以便通過將這些對象與任意Lock實現(xiàn)組合使用。其中奥吩,Lock替代了synchronized方法和語句的使用具伍,Condition替代了 Object 監(jiān)視器方法的使用。Condition的實例實質(zhì)上被綁定到一個鎖上圈驼。要為特定Lock實例獲得Condition實例,可使用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鎖綁定
* 因此實際上依然是讓持有特定鎖的線程進入等待
*/
con.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = count + "個" + name;
System.out.println(Thread.currentThread().getName() + "生產(chǎn)了" + this.name);
count++;
flag = true;
// 同理用condition對象喚醒所有持有l(wèi)ock鎖的線程
con.signalAll();;
}
finally{
lock.unlock();
}
}
// 消費產(chǎn)品的功能
public void consume() {
// 使用同一把鎖
lock.lock();
try{
while (!flag) {
try {
con.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "消費了" + this.name);
flag = false;
con.signalAll();;
}
finally{
lock.unlock();
}
}
}
此時結(jié)果如下:
雖然目前使用Lock接口以及Condition接口實現(xiàn)了生產(chǎn)者與消費者绩脆,但依然是喚醒全部線程,程序性能并沒有提升,此時就可以使用Lock接口與Condition接口的靈活性來避免這個問題靴迫。
3惕味、JDK1.5版本后的多線程程序
在JDK1.5版本之前玉锌,只能使用synchronized方法實現(xiàn)同步名挥,但synchronized方法的局限性是,wait()方法主守、notify()方法都必須放在synchronized代碼塊內(nèi)部禀倔,且操作鎖上線程的方法與鎖都是綁定在一起的,但是JDK1.5版本之后可以使用Lock接口直接創(chuàng)建一把鎖参淫,而這把鎖可以使用newCondition()方法創(chuàng)建多個Condition對象救湖,每一個對象都可以單獨實現(xiàn)await()、signa()等方法涎才,但實際上這些Condition對象仍然使用的是同一把鎖鞋既,因此就實現(xiàn)了單獨控制線程而不需要統(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對象,控制消費線程的等待與喚醒
private Condition con2 = lock.newCondition();
// 生產(chǎn)產(chǎn)品的功能
public void produce(String name) {
lock.lock();
try{
while (flag) {
try {
// 生產(chǎn)線程con1進入等待
con1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = count + "個" + name;
System.out.println(Thread.currentThread().getName() + "生產(chǎn)了" + this.name);
count++;
flag = true;
// 喚醒消費線程con2
con2.signal();;
}
finally{
lock.unlock();
}
}
// 消費產(chǎn)品的功能
public void consume() {
lock.lock();
try{
while (!flag) {
try {
// 消費線程con2進入等待
con2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "消費了" + 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("筆記本");
}
}
}
//消費任務(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)線程與消費線程棕兼,是因為線程T0陡舅、T1執(zhí)行的就是producer方法,在執(zhí)行該方法的con1.await();代碼時程储,T0或T1線程就會等待蹭沛,因此也就保證con1與生產(chǎn)線程實現(xiàn)了綁定,此時con2喚醒的只能是T2或T3線程章鲤,同理摊灭,消費線程也是如此,實現(xiàn)了程序性能的提升败徊。
4帚呼、使用Lock接口與Condition接口實現(xiàn)生產(chǎn)者與消費者的完整示例代碼
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)品價格
private double price;
// 存放產(chǎn)品的容器
private Clothes[] arr = new Clothes[100];
// 創(chuàng)建生產(chǎn)使用的下標
private int propointer;
// 創(chuàng)建消費使用的下標
private int conpointer;
// 創(chuàng)建一把鎖
private Lock lock = new ReentrantLock();
// 得到與鎖綁定的condition對象,控制生產(chǎn)線程的等待與喚醒
private Condition pro = lock.newCondition();
// 得到與鎖綁定的condition對象皱蹦,控制消費線程的等待與喚醒
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)容器滿時不生產(chǎn)
* 即產(chǎn)品數(shù)量與容器容量相同
*/
while (count == arr.length) {
try {
// 生產(chǎn)線程pro進入等待
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)下標
* 如果下標加一之后與容量相同
* 說明容器已滿 下標清零
*/
if (++propointer == arr.length) {
propointer = 0;
}
// 喚醒消費線程con
con.signal();
;
} finally {
lock.unlock();
}
}
/*
* 因為輸出arr[propointer]
* 實際上Clothes類型的對象
* 輸出對象默認調(diào)用其toString方法
* 因此還需要重寫該方法
*/
public String toString() {
return "價格為" + price + "英鎊的" + name;
}
// 消費產(chǎn)品的功能
public void consume() {
lock.lock();
try {
/*
* 先判斷是否可以消費
* 當(dāng)產(chǎn)品數(shù)量為0時
* 不能消費
*/
while (count == 0) {
try {
// 消費線程con進入等待
con.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 消費一件產(chǎn)品
Clothes clothes = arr[conpointer];
System.out.println(Thread.currentThread().getName() + "消費了" + clothes);
// 產(chǎn)品總量減一
count--;
/*
* 判斷消費下標
* 如果下標加一之后與容量相同
* 說明已取到最后一件產(chǎn)品
* 下標清零
*/
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();
}
}
}
// 消費任務(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();
}
}
此時結(jié)果如下:
版權(quán)聲明:歡迎轉(zhuǎn)載,歡迎擴散沪哺,但轉(zhuǎn)載時請標明作者以及原文出處沈自,謝謝合作! ↓↓↓