Java線程:新特徵-條件變量
條件變量是Java5線程中很重要的一個概念,顧名思義,條件變量就是表示條件的一種變量。可是必須說明,這裏的條件是沒有實際含義的,僅僅是個標記而已,而且條件的含義每每經過代碼來賦予其含義。
這裏的條件和普通意義上的條件表達式有着天壤之別。
條件變量都實現了java.util.concurrent.locks.Condition接口,條件變量的實例化是經過一個Lock對象上調用newCondition()方法來獲取的,這樣,條件就和一個鎖對象綁定起來了。所以,Java中的條件變量只能和鎖配合使用,來控制併發程序訪問競爭資源的安全。
條件變量的出現是爲了更精細控制線程等待與喚醒,在Java5以前,線程的等待與喚醒依靠的是Object對象的wait()和notify()/notifyAll()方法,這樣的處理不夠精細。
而在Java5中,一個鎖能夠有多個條件,每一個條件上能夠有多個線程等待,經過調用await()方法,可讓線程在該條件下等待。當調用signalAll()方法,又能夠喚醒該條件下的等待的線程。有關Condition接口的API能夠具體參考JavaAPI文檔。
條件變量比較抽象,緣由是他不是天然語言中的條件概念,而是程序控制的一種手段。
下面以一個銀行存取款的模擬程序爲例來揭蓋Java多線程條件變量的神祕面紗:
有一個帳戶,多個用戶(線程)在同時操做這個帳戶,有的存款有的取款,存款隨便存,取款有限制,不能透支,任何試圖透支的操做都將等待裏面有足夠存款才執行操做。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Java線程:條件變量
*
* @author leizhimin 2009-11-5 10:57:29
*/
public
class Test {
public
static
void main(String[] args) {
//建立併發訪問的帳戶
MyCount myCount =
new MyCount(
"95599200901215522", 10000);
//建立一個線程池
ExecutorService pool = Executors.newFixedThreadPool(2);
Thread t1 =
new SaveThread(
"張三", myCount, 2000);
Thread t2 =
new SaveThread(
"李四", myCount, 3600);
Thread t3 =
new DrawThread(
"王五", myCount, 2700);
Thread t4 =
new SaveThread(
"老張", myCount, 600);
Thread t5 =
new DrawThread(
"老牛", myCount, 1300);
Thread t6 =
new DrawThread(
"胖子", myCount, 800);
//執行各個線程
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
pool.execute(t6);
//關閉線程池
pool.shutdown();
}
}
/**
* 存款線程類
*/
class SaveThread
extends Thread {
private String name;
//操做人
private MyCount myCount;
//帳戶
private
int x;
//存款金額
SaveThread(String name, MyCount myCount,
int x) {
this.name = name;
this.myCount = myCount;
this.x = x;
}
public
void run() {
myCount.saving(x, name);
}
}
/**
* 取款線程類
*/
class DrawThread
extends Thread {
private String name;
//操做人
private MyCount myCount;
//帳戶
private
int x;
//存款金額
DrawThread(String name, MyCount myCount,
int x) {
this.name = name;
this.myCount = myCount;
this.x = x;
}
public
void run() {
myCount.drawing(x, name);
}
}
/**
* 普通銀行帳戶,不可透支
*/
class MyCount {
private String oid;
//帳號
private
int cash;
//帳戶餘額
private Lock lock =
new ReentrantLock();
//帳戶鎖
private Condition _save = lock.newCondition();
//存款條件
private Condition _draw = lock.newCondition();
//取款條件
MyCount(String oid,
int cash) {
this.oid = oid;
this.cash = cash;
}
/**
* 存款
*
* @param x 操做金額
* @param name 操做人
*/
public
void saving(
int x, String name) {
lock.lock();
//獲取鎖
if (x > 0) {
cash += x;
//存款
System.out.println(name +
"存款" + x +
",當前餘額爲" + cash);
}
_draw.signalAll();
//喚醒全部等待線程。
lock.unlock();
//釋放鎖
}
/**
* 取款
*
* @param x 操做金額
* @param name 操做人
*/
public
void drawing(
int x, String name) {
lock.lock();
//獲取鎖
try {
if (cash - x < 0) {
_draw.await();
//阻塞取款操做
}
else {
cash -= x;
//取款
System.out.println(name +
"取款" + x +
",當前餘額爲" + cash);
}
_save.signalAll();
//喚醒全部存款操做
}
catch (InterruptedException e) {
e.printStackTrace();
}
finally {
lock.unlock();
//釋放鎖
}
}
}
李四存款3600,當前餘額爲13600
張三存款2000,當前餘額爲15600
老張存款600,當前餘額爲16200
老牛取款1300,當前餘額爲14900
胖子取款800,當前餘額爲14100
王五取款2700,當前餘額爲11400
Process finished with exit code 0
假如咱們不用鎖和條件變量,如何實現此功能呢?下面是實現代碼:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Java線程:不用條件變量
*
* @author leizhimin 2009-11-5 10:57:29
*/
public
class Test {
public
static
void main(String[] args) {
//建立併發訪問的帳戶
MyCount myCount =
new MyCount(
"95599200901215522", 10000);
//建立一個線程池
ExecutorService pool = Executors.newFixedThreadPool(2);
Thread t1 =
new SaveThread(
"張三", myCount, 2000);
Thread t2 =
new SaveThread(
"李四", myCount, 3600);
Thread t3 =
new DrawThread(
"王五", myCount, 2700);
Thread t4 =
new SaveThread(
"老張", myCount, 600);
Thread t5 =
new DrawThread(
"老牛", myCount, 1300);
Thread t6 =
new DrawThread(
"胖子", myCount, 800);
//執行各個線程
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
pool.execute(t6);
//關閉線程池
pool.shutdown();
}
}
/**
* 存款線程類
*/
class SaveThread
extends Thread {
private String name;
//操做人
private MyCount myCount;
//帳戶
private
int x;
//存款金額
SaveThread(String name, MyCount myCount,
int x) {
this.name = name;
this.myCount = myCount;
this.x = x;
}
public
void run() {
myCount.saving(x, name);
}
}
/**
* 取款線程類
*/
class DrawThread
extends Thread {
private String name;
//操做人
private MyCount myCount;
//帳戶
private
int x;
//存款金額
DrawThread(String name, MyCount myCount,
int x) {
this.name = name;
this.myCount = myCount;
this.x = x;
}
public
void run() {
myCount.drawing(x, name);
}
}
/**
* 普通銀行帳戶,不可透支
*/
class MyCount {
private String oid;
//帳號
private
int cash;
//帳戶餘額
MyCount(String oid,
int cash) {
this.oid = oid;
this.cash = cash;
}
/**
* 存款
*
* @param x 操做金額
* @param name 操做人
*/
public
synchronized
void saving(
int x, String name) {
if (x > 0) {
cash += x;
//存款
System.out.println(name +
"存款" + x +
",當前餘額爲" + cash);
}
notifyAll();
//喚醒全部等待線程。
}
/**
* 取款
*
* @param x 操做金額
* @param name 操做人
*/
public
synchronized
void drawing(
int x, String name) {
if (cash - x < 0) {
try {
wait();
}
catch (InterruptedException e1) {
e1.printStackTrace();
}
}
else {
cash -= x;
//取款
System.out.println(name +
"取款" + x +
",當前餘額爲" + cash);
}
notifyAll();
//喚醒全部存款操做
}
}
輸出結果爲:
李四存款3600,當前餘額爲13600
王五取款2700,當前餘額爲10900
老張存款600,當前餘額爲11500
老牛取款1300,當前餘額爲10200
胖子取款800,當前餘額爲9400
張三存款2000,當前餘額爲11400
Process finished with exit code 0
結合先前同步代碼知識,觸類旁通,將此例改成同步代碼塊來實現,代碼以下:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Java線程:改成同步代碼塊
*
* @author leizhimin 2009-11-5 10:57:29
*/
public
class Test {
public
static
void main(String[] args) {
//建立併發訪問的帳戶
MyCount myCount =
new MyCount(
"95599200901215522", 10000);
//建立一個線程池
ExecutorService pool = Executors.newFixedThreadPool(2);
Thread t1 =
new SaveThread(
"張三", myCount, 2000);
Thread t2 =
new SaveThread(
"李四", myCount, 3600);
Thread t3 =
new DrawThread(
"王五", myCount, 2700);
Thread t4 =
new SaveThread(
"老張", myCount, 600);
Thread t5 =
new DrawThread(
"老牛", myCount, 1300);
Thread t6 =
new DrawThread(
"胖子", myCount, 800);
//執行各個線程
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
pool.execute(t6);
//關閉線程池
pool.shutdown();
}
}
/**
* 存款線程類
*/
class SaveThread
extends Thread {
private String name;
//操做人
private MyCount myCount;
//帳戶
private
int x;
//存款金額
SaveThread(String name, MyCount myCount,
int x) {
this.name = name;
this.myCount = myCount;
this.x = x;
}
public
void run() {
myCount.saving(x, name);
}
}
/**
* 取款線程類
*/
class DrawThread
extends Thread {
private String name;
//操做人
private MyCount myCount;
//帳戶
private
int x;
//存款金額
DrawThread(String name, MyCount myCount,
int x) {
this.name = name;
this.myCount = myCount;
this.x = x;
}
public
void run() {
myCount.drawing(x, name);
}
}
/**
* 普通銀行帳戶,不可透支
*/
class MyCount {
private String oid;
//帳號
private
int cash;
//帳戶餘額
MyCount(String oid,
int cash) {
this.oid = oid;
this.cash = cash;
}
/**
* 存款
*
* @param x 操做金額
* @param name 操做人
*/
public
void saving(
int x, String name) {
if (x > 0) {
synchronized (
this) {
cash += x;
//存款
System.out.println(name +
"存款" + x +
",當前餘額爲" + cash);
notifyAll();
//喚醒全部等待線程。
}
}
}
/**
* 取款
*
* @param x 操做金額
* @param name 操做人
*/
public
synchronized
void drawing(
int x, String name) {
synchronized (
this) {
if (cash - x < 0) {
try {
wait();
}
catch (InterruptedException e1) {
e1.printStackTrace();
}
}
else {
cash -= x;
//取款
System.out.println(name +
"取款" + x +
",當前餘額爲" + cash);
}
}
notifyAll();
//喚醒全部存款操做
}
}
李四存款3600,當前餘額爲13600
王五取款2700,當前餘額爲10900
老張存款600,當前餘額爲11500
老牛取款1300,當前餘額爲10200
胖子取款800,當前餘額爲9400
張三存款2000,當前餘額爲11400
Process finished with exit code 0
對比以上三種方式,從控制角度上講,第一種最靈活,第二種代碼最簡單,第三種容易犯錯。