程式設計模擬三個售票視窗售票100張,分別使用繼承Thread類和實現Runnable介面的方法,並分析有什麼問題?
1.使用繼承Thread的方法:
package li.thread;
//使用多執行緒,模擬三個視窗同時售票共100張
public class SellTicket {
public static void main(String[] args) {
SellTicket01 sellTicket01 = new SellTicket01();
SellTicket01 sellTicket02 = new SellTicket01();
SellTicket01 sellTicket03 = new SellTicket01();
sellTicket01.start();//啟動售票執行緒
sellTicket02.start();//啟動售票執行緒
sellTicket03.start();//啟動售票執行緒
}
}
//1.使用繼承Thread類的方式
class SellTicket01 extends Thread {
//多個物件共用同一個靜態成員變數(多個範例的static變數會共用同一塊記憶體區域)
private static int ticketNum = 100;//讓多個執行緒共用ticketNum
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
System.out.println("售票結束...");
break;
}
//休眠50毫秒,模擬
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("視窗:" + Thread.currentThread().getName() + "售出一張票 "
+ "剩餘票數:" + (--ticketNum));
}
}
}
一個顯然的問題是,剩餘票數竟然是負數!
原因是:每個執行緒都要進行票數判斷才能進行下一步操作,假設某時刻票數還剩2張,此時執行緒0判斷條件ticketNum <= 0不成立;於此同時,執行緒1執行緒2也同時進行了判斷,三者都通過了判斷,於是都認為此刻票數為2,都進行-1售票操作。於是三者結束後就會出現總票數為-1 的情況。
可以看到,造成票數超賣的主要原因是三個執行緒同時操作一個資源。
2.使用實現介面Runnable的方式:
package li.thread;
//使用多執行緒,模擬三個視窗同時售票共100張
public class SellTicket {
public static void main(String[] args) {
SellTicket02 sellTicket02 = new SellTicket02();
new Thread(sellTicket02).start();//第1個執行緒-視窗
new Thread(sellTicket02).start();//第2個執行緒-視窗
new Thread(sellTicket02).start();//第3個執行緒-視窗
}
}
class SellTicket02 implements Runnable {
private int ticketNum = 100;
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
System.out.println("售票結束...");
break;
}
//休眠50毫秒,模擬
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("視窗:" + Thread.currentThread().getName() + "售出一張票 "
+ "剩餘票數:" + (--ticketNum));
}
}
}
可以看到,實現介面Runnable的方式同樣發生了票數為負數的情況,原因與上面一致,是由於多個執行緒同時操作一個資源而造成的。
要解決類似的問題,就要引入執行緒的同步和互斥的概念。該問題將在之後解決。
例子:
啟動一個執行緒t,要求在main執行緒中去停止執行緒t,請程式設計實現。
package li.thread.exit_;
public class ThreadExit_ {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.start();
//如果希望main執行緒可以去控制 t1執行緒的終止,必須可以修改loop
//讓 t1退出run方法,從而終止 t1執行緒 -->稱為 通知方式
//讓主執行緒休眠 10秒,在通知 t1執行緒退出
System.out.println("主執行緒休眠10秒...");
Thread.sleep(10*1000);
t.setLoop(false);
}
}
class T extends Thread {
int count = 0;
//設定一個控制變數
private boolean loop = true;
@Override
public void run() {
while (loop) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T 執行中..."+(++count));
}
}
public void setLoop(boolean loop) {
this.loop = loop;
}
}
可以用於一個執行緒通過變數控制另一個執行緒終止的情況。
注意事項和細節:
例子1:
package li.thread.method;
public class ThreadMethod01 {
public static void main(String[] args) throws InterruptedException {
//測試相關方法
T t = new T();
t.setName("jack");//設定執行緒的名稱
t.setPriority(Thread.MIN_PRIORITY);
t.start();//啟動子執行緒
//主執行緒列印5句hi,然後中斷子執行緒的休眠
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println("hi" + i);
}
System.out.println(t.getName() + "執行緒的優先順序=" + t.getPriority());
t.interrupt();//當執行到這裡的時候,就會中斷 t執行緒的休眠
}
}
class T extends Thread {//自定義的執行緒類
@Override
public void run() {
while (true) {//每隔5秒吃100個包子,然後休眠5秒,再吃...
for (int i = 0; i < 100; i++) {
//Thread.currentThread().getName()獲取當前執行緒的名稱
System.out.println(Thread.currentThread().getName() + "吃包子~~~" + i);
}
try {
System.out.println(Thread.currentThread().getName() + "休眠中~~~");
sleep(20000);//休眠20秒
} catch (InterruptedException e) {
//當該執行緒執行到一個interrupt方法時,就會catch一個異常,可以加入自己的業務程式碼
//InterruptedException是捕獲到一箇中斷異常
System.out.println(Thread.currentThread().getName() + "被interrupt了");
}
}
}
}
yield:執行緒的禮讓。讓出cpu,讓其他執行緒執行,但禮讓的時間不確定,所以也不一定禮讓成功。
join:執行緒的插隊。插隊的執行緒一旦插隊成功,則肯定先執行完插入的執行緒的所有任務
案例:建立一個子執行緒,每個1秒輸出hello,輸出20次;主執行緒每隔1秒輸出hi,輸出20次。要求:兩個執行緒同時執行,當主執行緒輸出5次後,就讓子執行緒執行完畢,主執行緒再繼續。