Java多執行緒模擬售票系統
阿新 • • 發佈:2019-01-09
Java建立多執行緒的兩種基本方法:
方法1.繼承Thread類
(1)定義子類,繼承Thread類,重寫該類的run()方法作為執行緒執行體;
(2)建立該子類的例項作為執行緒物件;
(3)呼叫執行緒物件的start()方法來啟動執行緒;
我們以模擬火車售票系統為例:
public class SellTicket { public static void main(String[] args) { for(int i=1; i<4; i++){ TicketWindow tw = new TicketWindow(); tw.setName("TicketWindow-" + i); tw.start(); } } } class TicketWindow extends Thread{ private int tickets = 100;//車票總量 @Override public void run(){ while(true){ if(tickets>0){ System.out.println(Thread.currentThread().getName() + "準備出票,剩餘票數:" + tickets + "張"); tickets--; System.out.println(Thread.currentThread().getName() + "賣出一張,剩餘票數:" + tickets + "張"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } else{ System.out.println(Thread.currentThread().getName() + "餘票不足,停止售票!"); break; } } } }
方法2.實現Runnable介面
(1)定義類實現Runnable介面,重寫run()方法;(2)建立該實現類的例項,以該例項作為Thread的target來建立Thread物件;
(3)呼叫該Thread物件的start()方法來啟動該執行緒;
還是以模擬火車售票視窗為例:
public class SellTicket { public static void main(String[] args) { TicketWindow tw = new TicketWindow(); for(int i=1; i<4; i++){ Thread t = new Thread(tw,"TickWindow-" + i); t.start(); } } } class TicketWindow implements Runnable{ private int tickets = 100;//車票總量 @Override public void run(){ while(true){ if(tickets>0){ System.out.println(Thread.currentThread().getName() + "準備出票,剩餘票數:" + tickets + "張"); tickets--; System.out.println(Thread.currentThread().getName() + "賣出一張,剩餘票數:" + tickets + "張"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } else{ System.out.println(Thread.currentThread().getName() + "餘票不足,停止售票!"); break; } } } }
注意:
繼承Thread類與實現Runnable介面的區別及優缺點對比:
繼承Thread類 | 實現Runnable介面 | |
能否繼承其他類 | 不能 | 能 |
如何訪問當前執行緒 | this即為當前執行緒 | Thread.currentThread() |
是否共享同一target | 否 | 是,適合多個相同執行緒處理同一份資源的情況 |
雖然上面實現Runnable介面的火車售票系統共享了車票總數,但是沒有控制同一時刻只能有一個執行緒進行賣票操作,因此需要同步關鍵字 synchronized 進行控制,
執行緒同步分為同步塊和同步方法:
1.同步塊
同步塊的語法格式為:
synchronized(object){ //...同步程式碼塊 }
上述程式碼的意思是,要想執行同步程式碼塊,必須先獲得同步監視器的鎖定.
其中object為同步監視器,一般使用可能被併發訪問的共享資源當同步監視器;
下面給出java多執行緒模擬火車售票系統的同步塊實現:
public static void main(String[] args) {
TicketWindow tw = new TicketWindow();
for(int i=1; i<4; i++){
Thread t = new Thread(tw,"TickWindow-" + i);
t.start();
}
}
}
class TicketWindow implements Runnable{
private int tickets = 10;//車票總量
@Override
public void run(){
while(true){
synchronized (this) {
if(tickets>0){
System.out.println(Thread.currentThread().getName() + "準備出票,剩餘票數:" + tickets + "張");
tickets--;
System.out.println(Thread.currentThread().getName() + "賣出一張,剩餘票數:" + tickets + "張");
try {
//休眠100ms賣票完會報錯ERROR: JDWP Unable to get JNI 1.2 environment, jvm->GetEnv() return code = -2JDWP exit error AGENT_ERROR_NO_JNI_ENV(183): [../../../src/share/back/util.c:820]
//Thread.sleep(100);
Thread.sleep(500);//出票成功後讓當前售票視窗睡眠,以便讓其他售票視窗賣票
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else{
System.out.println(Thread.currentThread().getName() + "餘票不足,停止售票!");
break;
}
}
}
}
}
輸出:
TickWindow-1準備出票,剩餘票數:10張
TickWindow-1賣出一張,剩餘票數:9張
TickWindow-1準備出票,剩餘票數:9張
TickWindow-1賣出一張,剩餘票數:8張
TickWindow-1準備出票,剩餘票數:8張
TickWindow-1賣出一張,剩餘票數:7張
TickWindow-1準備出票,剩餘票數:7張
TickWindow-1賣出一張,剩餘票數:6張
TickWindow-1準備出票,剩餘票數:6張
TickWindow-1賣出一張,剩餘票數:5張
TickWindow-1準備出票,剩餘票數:5張
TickWindow-1賣出一張,剩餘票數:4張
TickWindow-1準備出票,剩餘票數:4張
TickWindow-1賣出一張,剩餘票數:3張
TickWindow-1準備出票,剩餘票數:3張
TickWindow-1賣出一張,剩餘票數:2張
TickWindow-3準備出票,剩餘票數:2張
TickWindow-3賣出一張,剩餘票數:1張
TickWindow-3準備出票,剩餘票數:1張
TickWindow-3賣出一張,剩餘票數:0張
TickWindow-3餘票不足,停止售票!
TickWindow-2餘票不足,停止售票!
TickWindow-1餘票不足,停止售票!
2.同步方法.
同步方法就是使用 synchronized 關鍵字修飾某個方法,synchronized 修飾的例項方法(非static方法)的同步監視器是this.
使用同步方法解決共享資源的多執行緒訪問衝突的一般方式是:
//把修改共享資源的方法使用synchronized進行修飾
public synchronized void someMethod(){
//do something...
}
//在run()方法中呼叫該同步方法
public void run(){
someMethod();
}
下面給出使用同步方法實現的模擬火車售票系統:
public class SellTicket {
public static void main(String[] args) {
TicketWindow tw = new TicketWindow();
for(int i=1; i<4; i++){
Thread t = new Thread(tw,"TickWindow-" + i);
t.start();
}
}
}
class TicketWindow implements Runnable{
private int tickets = 10;//車票總量
@Override
public void run(){
while(true){
sellTicket();
}
}
public synchronized void sellTicket(){
if(tickets>0){
System.out.println(Thread.currentThread().getName() + "準備出票,剩餘票數:" + tickets + "張");
tickets--;
System.out.println(Thread.currentThread().getName() + "賣出一張,剩餘票數:" + tickets + "張");
try {
Thread.sleep(500);//出票成功後讓當前售票視窗睡眠,以便讓其他售票視窗賣票
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else{
System.out.println(Thread.currentThread().getName() + "餘票不足,停止售票!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
輸出:
TickWindow-1準備出票,剩餘票數:10張
TickWindow-1賣出一張,剩餘票數:9張
TickWindow-1準備出票,剩餘票數:9張
TickWindow-1賣出一張,剩餘票數:8張
TickWindow-1準備出票,剩餘票數:8張
TickWindow-1賣出一張,剩餘票數:7張
TickWindow-1準備出票,剩餘票數:7張
TickWindow-1賣出一張,剩餘票數:6張
TickWindow-1準備出票,剩餘票數:6張
TickWindow-1賣出一張,剩餘票數:5張
TickWindow-1準備出票,剩餘票數:5張
TickWindow-1賣出一張,剩餘票數:4張
TickWindow-1準備出票,剩餘票數:4張
TickWindow-1賣出一張,剩餘票數:3張
TickWindow-1準備出票,剩餘票數:3張
TickWindow-1賣出一張,剩餘票數:2張
TickWindow-1準備出票,剩餘票數:2張
TickWindow-1賣出一張,剩餘票數:1張
TickWindow-1準備出票,剩餘票數:1張
TickWindow-1賣出一張,剩餘票數:0張
TickWindow-1餘票不足,停止售票!
TickWindow-1餘票不足,停止售票!
TickWindow-1餘票不足,停止售票!
TickWindow-1餘票不足,停止售票!
TickWindow-1餘票不足,停止售票!
TickWindow-3餘票不足,停止售票!
TickWindow-2餘票不足,停止售票!
TickWindow-3餘票不足,停止售票!
TickWindow-3餘票不足,停止售票!
注意:while(true){}要放在run()方法裡面,而不是sell方法裡. 否則會出現某一個視窗一直把票賣完的情況.
要想讓售票視窗在售完票之後停止,需要在run()方法裡的作條件限制,修改如下:
public class SellTicket {
public static void main(String[] args) {
TicketWindow tw = new TicketWindow();
for(int i=1; i<4; i++){
Thread t = new Thread(tw,"TickWindow-" + i);
t.start();
}
}
}
class TicketWindow implements Runnable{
private int tickets = 10;//車票總量
@Override
public void run(){
while(true){
if(tickets>0){
sellTicket();
}
else{
System.out.println(Thread.currentThread().getName() + "餘票不足,停止售票!");
break;
}
}
}
public synchronized void sellTicket(){
if(tickets>0){
System.out.println(Thread.currentThread().getName() + "準備出票,剩餘票數:" + tickets + "張");
tickets--;
System.out.println(Thread.currentThread().getName() + "賣出一張,剩餘票數:" + tickets + "張");
try {
Thread.sleep(500);//出票成功後讓當前售票視窗睡眠,以便讓其他售票視窗賣票
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
輸出如下:
TickWindow-1準備出票,剩餘票數:10張
TickWindow-1賣出一張,剩餘票數:9張
TickWindow-1準備出票,剩餘票數:9張
TickWindow-1賣出一張,剩餘票數:8張
TickWindow-1準備出票,剩餘票數:8張
TickWindow-1賣出一張,剩餘票數:7張
TickWindow-1準備出票,剩餘票數:7張
TickWindow-1賣出一張,剩餘票數:6張
TickWindow-1準備出票,剩餘票數:6張
TickWindow-1賣出一張,剩餘票數:5張
TickWindow-1準備出票,剩餘票數:5張
TickWindow-1賣出一張,剩餘票數:4張
TickWindow-1準備出票,剩餘票數:4張
TickWindow-1賣出一張,剩餘票數:3張
TickWindow-1準備出票,剩餘票數:3張
TickWindow-1賣出一張,剩餘票數:2張
TickWindow-1準備出票,剩餘票數:2張
TickWindow-1賣出一張,剩餘票數:1張
TickWindow-1準備出票,剩餘票數:1張
TickWindow-1賣出一張,剩餘票數:0張
TickWindow-1餘票不足,停止售票!
TickWindow-3餘票不足,停止售票!
TickWindow-2餘票不足,停止售票!
注意!
使用 synchronized 修飾run()方法是無效的:
public class SellTicket {
public static void main(String[] args) {
TicketWindow tw = new TicketWindow();
for(int i=1; i<4; i++){
Thread t = new Thread(tw,"TickWindow-" + i);
t.start();
}
}
}
class TicketWindow implements Runnable{
private int tickets = 10;//車票總量
@Override
public synchronized void run(){
while(true){
if(tickets>0){
System.out.println(Thread.currentThread().getName() + "準備出票,剩餘票數:" + tickets + "張");
tickets--;
System.out.println(Thread.currentThread().getName() + "賣出一張,剩餘票數:" + tickets + "張");
try {
Thread.sleep(500);//出票成功後讓當前售票視窗睡眠,以便讓其他售票視窗賣票
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else{
System.out.println(Thread.currentThread().getName() + "餘票不足,停止售票!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
會輸出:
TickWindow-1準備出票,剩餘票數:10張
TickWindow-1賣出一張,剩餘票數:9張
TickWindow-1準備出票,剩餘票數:9張
TickWindow-1賣出一張,剩餘票數:8張
TickWindow-1準備出票,剩餘票數:8張
TickWindow-1賣出一張,剩餘票數:7張
TickWindow-1準備出票,剩餘票數:7張
TickWindow-1賣出一張,剩餘票數:6張
TickWindow-1準備出票,剩餘票數:6張
TickWindow-1賣出一張,剩餘票數:5張
TickWindow-1準備出票,剩餘票數:5張
TickWindow-1賣出一張,剩餘票數:4張
TickWindow-1準備出票,剩餘票數:4張
TickWindow-1賣出一張,剩餘票數:3張
TickWindow-1準備出票,剩餘票數:3張
TickWindow-1賣出一張,剩餘票數:2張
TickWindow-1準備出票,剩餘票數:2張
TickWindow-1賣出一張,剩餘票數:1張
TickWindow-1準備出票,剩餘票數:1張
TickWindow-1賣出一張,剩餘票數:0張
TickWindow-1餘票不足,停止售票!
TickWindow-1餘票不足,停止售票!
TickWindow-1餘票不足,停止售票!
TickWindow-1餘票不足,停止售票!
原因見:
參考: