線程基礎02 3.繼承Thread和實現Runnable的區別 從java的設計來看,通過繼承Thread或者實現Runnable介面本身來創建線程本質上沒有區別,從jdk幫助文檔我們可以看到Thread類本身就實現了Runnable介面 實現Runnable介面方式更加適合多個線程共用一個資源的情 ...
線程基礎02
3.繼承Thread和實現Runnable的區別
- 從java的設計來看,通過繼承Thread或者實現Runnable介面本身來創建線程本質上沒有區別,從jdk幫助文檔我們可以看到Thread類本身就實現了Runnable介面
- 實現Runnable介面方式更加適合多個線程共用一個資源的情況,並且避免了單繼承的限制,建議使用Runnable介面
3.1多線程售票問題
編程模擬三個售票視窗售票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的方式同樣發生了票數為負數的情況,原因與上面一致,是由於多個線程同時操作一個資源而造成的。
要解決類似的問題,就要引入線程的同步和互斥的概念。該問題將在之後解決。
4.線程終止
- 基本說明:
- 當線程完成任務後,會自動退出
- 還可以通過使用變數來控制run方法退出的方式來停止線程,即通知方式
例子:
啟動一個線程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;
}
}
可以用於一個線程通過變數控制另一個線程終止的情況。
5.線程常用方法
- 常用方法第一組:
- setName //設置線程名稱,使之與參數name相同
- getName //返回該線程的名稱
- start //使該線程開始執行;Java虛擬機底層調用該線程的start0()方法
- run //調用線程對象run方法
- setPriority //更改線程的優先順序
- getPriority // 獲取線程的優先順序
- sleep //在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行)
- interrupt //中斷線程
註意事項和細節:
- start方法底層會創建新的線程,調用run,run就是一個簡單的方法調用,不會啟動新的線程
- 線程優先順序的範圍
- interrupt,中斷線程,但並沒有真正地結束線程。所以一般用於中斷正在休眠的線程
- sleep:線程的靜態方法,使當前線程休眠
例子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次後,就讓子線程運行完畢,主線程再繼續。