實例:多線程實現(xiàn)網(wǎng)絡購票,用戶提交購票信息后,
第一步:網(wǎng)站修改網(wǎng)站車票數(shù)據(jù)
第二部;顯示出票反饋信息給用戶
線程類:
public class Site implements Runnable {
//記錄剩余票數(shù)
private int count = 10;
//記錄當前搶到第幾張票
private int num = 0;
public void run() {
//循環(huán),當剩余票數(shù)為0時,結束
while (true) {
if (count <= 0) {
break;
} else {
// 1、修改數(shù)據(jù)(剩余票數(shù),搶到第幾張票)
count--;
// 2、顯示信息,反饋用戶搶到第幾張票
num++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "搶到第" + num + "張票,剩余" + count + "張票");
}
}
}
}
測試類:
public class Test {
public static void main(String[] args) {
Site site = new Site();
Thread person1 = new Thread(site);
Thread person2 = new Thread(site);
Thread person3 = new Thread(site);
person1.start();
person2.start();
person3.start();
}
}
運行結果:
Thread-2搶到第3張票,剩余7張票
Thread-0搶到第3張票,剩余7張票
Thread-1搶到第3張票,剩余7張票
Thread-2搶到第6張票,剩余4張票
Thread-1搶到第6張票,剩余4張票
Thread-0搶到第6張票,剩余4張票
Thread-2搶到第9張票,剩余1張票
Thread-0搶到第9張票,剩余1張票
Thread-1搶到第9張票,剩余1張票
Thread-2搶到第10張票,剩余0張票
從運行結果看,簡直不能看,輸出的數(shù)據(jù)都是些什么東西啊。存在的問題如下:
1、不是從第一張票開始
2、存在多人搶到一張票的情況
3、有些票號沒有被搶到
當多個線程共享同一資源時,一個線程未完成全部操作的時候,其他線程修改數(shù)據(jù),造成數(shù)據(jù)的不安全問題。
原因分析:
第一個線程在休眠的1000ms中,其他的兩個線程也完成了搶票動作然后進入休眠。等第一個線程一覺醒來一臉懵逼地發(fā)現(xiàn)只剩七張票了。剩下兩個線程也是這樣。
解決思路:將這兩步操作綁定在一起,只有當一個線程全部完成這些操作時,才能允許其他線程進行操作。
修改
1、當用synchronized關鍵字來修改run方法時:
public synchronized void run() {
//循環(huán),當剩余票數(shù)為0時,結束
while (true) {
if (count == 0) {
break;
} else {
// 1、修改數(shù)據(jù)(剩余票數(shù),搶到第幾張票)
count--;
// 2、顯示信息,反饋用戶搶到第幾張票
num++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "搶到第" + num + "張票,剩余" + count + "張票");
}
}
}
輸出結果為:
Thread-0搶到第1張票,剩余9張票
Thread-0搶到第2張票,剩余8張票
Thread-0搶到第3張票,剩余7張票
Thread-0搶到第4張票,剩余6張票
Thread-0搶到第5張票,剩余5張票
Thread-0搶到第6張票,剩余4張票
Thread-0搶到第7張票,剩余3張票
Thread-0搶到第8張票,剩余2張票
Thread-0搶到第9張票,剩余1張票
Thread-0搶到第10張票,剩余0張票
此時第一個線程搶到了全部的票。因為第一個線程一直在run方法中的while中循環(huán),直到票被搶完才會終止。此時運行第二和第三個線程的時候沒有票可搶。
2、但是將代碼修改為以下時:
public void run() {
//循環(huán),當剩余票數(shù)為0時,結束
while (true) {
if (count <= 0) {
break;
} else {
this.a();
}
}
}
synchronized public void a() {
// 1、修改數(shù)據(jù)(剩余票數(shù),搶到第幾張票)
count--;
// 2、顯示信息,反饋用戶搶到第幾張票
num++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "搶到第" + num + "張票,剩余" + count + "張票");
}
輸出結果為:
Thread-0搶到第1張票,剩余9張票
Thread-2搶到第2張票,剩余8張票
Thread-1搶到第3張票,剩余7張票
Thread-1搶到第4張票,剩余6張票
Thread-1搶到第5張票,剩余5張票
Thread-1搶到第6張票,剩余4張票
Thread-1搶到第7張票,剩余3張票
Thread-1搶到第8張票,剩余2張票
Thread-1搶到第9張票,剩余1張票
Thread-1搶到第10張票,剩余0張票
Thread-2搶到第11張票,剩余-1張票
Thread-0搶到第12張票,剩余-2張票
從結果來看,多搶了兩張票。
當把if的控制條件改為以下時:
public void run() {
//循環(huán),當剩余票數(shù)為0時,結束
while (true) {
if (count <= 2) {
break;
} else {
this.a();
}
}
}
輸出結果為:
Thread-0搶到第1張票,剩余9張票
Thread-2搶到第2張票,剩余8張票
Thread-2搶到第3張票,剩余7張票
Thread-1搶到第4張票,剩余6張票
Thread-1搶到第5張票,剩余5張票
Thread-2搶到第6張票,剩余4張票
Thread-2搶到第7張票,剩余3張票
Thread-2搶到第8張票,剩余2張票
Thread-0搶到第9張票,剩余1張票
Thread-1搶到第10張票,剩余0張票
此時的結果沒問題,但是為什么?。吭谖铱磥?,控制條件無論如何也不應該是 <= 2。
原因可能如下:三個線程都會運行run方法,在運行過程中都走過了if的判斷條件,只是在else中的語句前停下,因為else中的語句設置了鎖,在他們等待運行的時間中,票數(shù)可能就改變了,變得小于0了,但是他們還是會運行完自己的語句。直到下一次運行進不去else的條件時為止。
同時,如果將條件改為count == 0的話,結果可能會更糟,因為在他們等待的時間中,票數(shù)可能已經(jīng)變化過去0了,一旦這樣的話就會陷入到一個死循環(huán)中。
3、同時這樣改也沒問題:定義了一個flag來控制while循環(huán)條件
public class Site implements Runnable {
//記錄剩余票數(shù)
private int count = 10;
//記錄當前搶到第幾張票
private int num = 0;
boolean flag = false;
public void run() {
//循環(huán),當剩余票數(shù)為0時,結束
while (!false) {
this.a();
}
}
synchronized public void a() {
if (count <= 0) {
flag = true;
return;
} else {
}
// 1、修改數(shù)據(jù)(剩余票數(shù),搶到第幾張票)
count--;
// 2、顯示信息,反饋用戶搶到第幾張票
num++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "搶到第" + num + "張票,剩余" + count + "張票");
}
}
此時結果為:
Thread-0搶到第1張票,剩余9張票
Thread-2搶到第2張票,剩余8張票
Thread-2搶到第3張票,剩余7張票
Thread-2搶到第4張票,剩余6張票
Thread-1搶到第5張票,剩余5張票
Thread-1搶到第6張票,剩余4張票
Thread-1搶到第7張票,剩余3張票
Thread-2搶到第8張票,剩余2張票
Thread-2搶到第9張票,剩余1張票
Thread-2搶到第10張票,剩余0張票
4、使用修改代碼塊的方式來修改如下:
public class Site implements Runnable {
//記錄剩余票數(shù)
private int count = 10;
//記錄當前搶到第幾張票
private int num = 0;
boolean flag = false;
public void run() {
//循環(huán),當剩余票數(shù)為0時,結束
while (!flag) {
synchronized (this) {
this.a();
}
}
}
synchronized public void a() {
if (count <= 0) {
flag = true;
return;
} else {
// 1、修改數(shù)據(jù)(剩余票數(shù),搶到第幾張票)
count--;
// 2、顯示信息,反饋用戶搶到第幾張票
num++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "搶到第" + num + "張票,剩余" + count + "張票");
}
}
}
運行結果如下:
Thread-0搶到第1張票,剩余9張票
Thread-0搶到第2張票,剩余8張票
Thread-0搶到第3張票,剩余7張票
Thread-0搶到第4張票,剩余6張票
Thread-0搶到第5張票,剩余5張票
Thread-0搶到第6張票,剩余4張票
Thread-2搶到第7張票,剩余3張票
Thread-1搶到第8張票,剩余2張票
Thread-1搶到第9張票,剩余1張票
Thread-1搶到第10張票,剩余0張票
總結
多個并發(fā)線程訪問同一資源的同步代碼塊時
1、同一時刻只能有一個線程進入synchronized同步代碼塊。
2、當一個線程訪問一個synchronized(this),其他synchronized(this)同步代碼塊同樣被鎖定
3、當一個線程訪問一個synchronized(this)同步代碼塊時,其他線程可以訪問該資源的非synchronized(this)同步代碼。
什么鎖,其實就是個廁所,你不出來我就進不去。