七、執行緒生命週期
沒錯,執行緒也是有生命週期的。就好像人類有出生、兒童、青年、中年、晚年、死亡一般。下面是執行緒的生命週期圖:
八、執行緒的安全問題
所謂執行緒不安全【併發問題】,舉個例子來說,如賣票,會出現重票、錯票等現象,這就是執行緒不安全的。
並行:多個CPU同時執行多個任務。比如:多個人同時做不同的事
併發:一個CPU(採用時間片)同時執行多個任務。比如:秒殺、多個人做同一件事。
如:
/**
* 建立三個視窗買票,共100張票。用Runnable介面實現
*/
public class RunMainRunnable {
public static void main(String[] args) {
Window window = new Window();
// 建立三個執行緒
Thread win1 = new Thread(window);
Thread win2 = new Thread(window);
Thread win3 = new Thread(window);
// 設定執行緒的名字
win1.setName("視窗一:");
win2.setName("視窗二:");
win3.setName("視窗三:");
// 啟動執行緒
win1.start();
win2.start();
win3.start();
}
}
class Window implements Runnable{
private int ticket = 100; // 定義100張票
@Override
public void run() {
while (true) {
if (ticket > 0) {
try {
Thread.sleep(100); // 呼叫此方法,讓效果明顯一點
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"獲取到了第"+ticket+"票");
ticket--;
}else{
break;
}
}
}
}
執行結果:出現了重票
問題分析:
之所以會出現重票、錯票等問題,就是因為出現了執行緒不安全。【假設此時ticket=7】在if判斷中,如當視窗一獲得cpu後,首先判斷ticket>0會真,執行列印語句輸出“視窗一:獲取到了第7票”,此時視窗二獲得cpu【注意:視窗一併未執行“ticket--;”,ticket依然為7】,判斷ticket>0為真,執行列印語句輸出“視窗二:獲取到了第7票”,此時視窗三獲得cpu【注意:視窗二並未執行“ticket--;”,ticket依然為7】,判斷ticket>0為真,執行列印語句輸出“視窗三:獲取到了第7票”。便會出現重票。這在實際生活當中,肯定是不允許的,那我們該如何解決執行緒的安全問題呢?
九、同步機制
在Java中,我們通過同步機制,來解決執行緒的安全問題。
方式一:同步程式碼塊
synchronized(同步監視器){ //需要被同步的程式碼 }
說明:
- 什麼是同步的程式碼?
- 操作共享資料的程式碼,即為需要被同步的程式碼。
- 什麼是共享資料?
- 多個執行緒共同操作的變數。比如:ticket就是共享資料。
- 什麼是同步監視器
- 俗稱:鎖。任何一個類的物件,都可以充當鎖。
- 要求:多個執行緒必須要共用同一把鎖。
方式二:同步方法
如果操作共享資料的程式碼完整的宣告在一個方法中,我們不妨將此方法宣告為同步的。
總結:
好處:同步的方式,解決了執行緒的安全問題。
壞處:操作同步程式碼時,只能有一個執行緒參與,其他執行緒等待。相當於是一個單執行緒的過程,效率低。 ---侷限性
eg1:方式一:同步程式碼塊
/**
* 建立三個視窗買票,共100張票。用Runnable介面實現
*/
public class RunMainRunnable {
public static void main(String[] args) {
Window window = new Window();
// 建立三個執行緒
Thread win1 = new Thread(window);
Thread win2 = new Thread(window);
Thread win3 = new Thread(window);
// 設定執行緒的名字
win1.setName("視窗一:");
win2.setName("視窗二:");
win3.setName("視窗三:");
// 啟動執行緒
win1.start();
win2.start();
win3.start();
}
}
/**
* 方式一;同步程式碼塊
*/
class Window implements Runnable{
private int ticket = 100; // 定義100張票
private Object object = new Object();
@Override
public void run() {
while (true) {
synchronized (object){ // 同步程式碼塊
if (ticket > 0) {
try {
Thread.sleep(100); // 呼叫此方法,讓效果明顯一點
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"獲取到了第"+ticket+"票");
ticket--;
}else{
break;
}
}
}
}
}
eg2:方式二:同步方法
/**
* 建立三個視窗買票,共100張票。用Runnable介面實現
*/
public class RunMainRunnable {
public static void main(String[] args) {
Window window = new Window();
// 建立三個執行緒
Thread win1 = new Thread(window);
Thread win2 = new Thread(window);
Thread win3 = new Thread(window);
// 設定執行緒的名字
win1.setName("視窗一:");
win2.setName("視窗二:");
win3.setName("視窗三:");
// 啟動執行緒
win1.start();
win2.start();
win3.start();
}
}
/*
方式二;同步方法
* */
class Window implements Runnable{
private int ticket = 100; // 定義100張票
@Override
public void run() {
while (true) {
show();// 呼叫同步方法
if (ticket == 0) {
break; // 用來結束迴圈
}
}
}
// 定義一個私有方法
private synchronized void show() { // 加synchronized關鍵字,使其成為一位同步方法。同步監視器或鎖即為:this
if (ticket > 0) {
try {
Thread.sleep(100); // 呼叫此方法,讓效果明顯一點
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"獲取到了第"+ticket+"票");
ticket--;
}
}
}