Day17 JavaSE 線程(下)

JavaSE 線程(下)

三、Thread類的常用方法

1 線程一般方法

方法名稱 功能
void start() 啓動線程
run() 線程在被調度時執行的操作
String getName() 取出線程名稱
void setName(String name) 設置線程名稱
static currentThread() 返回當前線程
int getPriority() 獲取線程優先級
void setPriority() 設置線程優先級(默認爲5)

補充:線程優先級就是哪個線程有較大的概率被執行。優先級用數字1-10表示,數字越大,優先級越高。若未設置優先級,默認爲5。

以上方法案例展示:

package com.thread;

public class Demo01 {
    public static void main(String[] args) {
        RunnalbalImpl run0 = new RunnalbalImpl();
        RunnalbalImpl run1 = new RunnalbalImpl();

        Thread t0 = new Thread(run0);
        Thread t1 = new Thread(run1);

        t1.setName("線程t1");

         /*
            線程優先級就是哪個線程有較大的概率被執行。
            優先級用數字1-10表示,數字越大,優先級越高。
            若爲設置,默認爲5。
         */
        t0.setPriority(1); //設置優先級爲1,若爲設置默認爲5

        t0.start();
        t1.start();

        System.out.println("t0的線程名稱: "+ t0.getName()); //若在創建線程時沒有指定名稱,則系統默認給出"Thread-0/1/..."
        System.out.println("t1的線程名稱: "+ t1.getName());

        System.out.println("t0的優先級: "+t0.getPriority()); //獲取線程優先級
    }
}
class RunnalbalImpl implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"Runnable多線程運行的代碼");
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+"這是Runnable多線程的邏輯代碼" + i);
        }
    }
}
/*運行結果:
Thread-0Runnable多線程運行的代碼
線程t1Runnable多線程運行的代碼
線程t1這是Runnable多線程的邏輯代碼0
線程t1這是Runnable多線程的邏輯代碼1
t0的線程名稱: Thread-0
線程t1這是Runnable多線程的邏輯代碼2
Thread-0這是Runnable多線程的邏輯代碼0
Thread-0這是Runnable多線程的邏輯代碼1
線程t1這是Runnable多線程的邏輯代碼3
Thread-0這是Runnable多線程的邏輯代碼2
線程t1這是Runnable多線程的邏輯代碼4
Thread-0這是Runnable多線程的邏輯代碼3
Thread-0這是Runnable多線程的邏輯代碼4
t1的線程名稱: 線程t1
t0的優先級: 1
 */

2 線程控制方法

方法名稱 功能
static void yield() 線程讓步:
1. 暫停當前正在執行的線程,把執行機會讓給優先級相同或更高的線程
2. 若隊列中沒有同優先級的線程,忽略此方法
join() 當某個程序執行流中調用其他線程的join()方法時,調用線程將被阻塞,直到·join()方法加入的join線程執行完爲止。
static void sleep(long millis) 1. 另當前活動線程在指定時間段內放棄對CPU控制,使其他線程有機會被執行,時間到後重新排隊。
2. 需要拋出InterruptedException異常
stop() 強制線程結束
boolean isAlive() 返回boolean值,判斷線程是否還存活

以上方法案例展示:

package com.thread;

public class Demo02 {
    public static void main(String[] args) {
        RunnalbalImpl2 run0 = new RunnalbalImpl2();
        RunnalbalImpl2 run1 = new RunnalbalImpl2();

        Thread t0 = new Thread(run0);
        Thread t1 = new Thread(run1);

        t0.start();
        t1.start();

        System.out.println("-----------------------------------------1");
        System.out.println("-----------------------------------------2");

        t1.stop(); //強制停止線程
        try {
            t0.join(); //相當於在這裏將t0的run方法插入到這個位置執行!
            /* 專業說法
                阻塞當前main方法,先不執行System.out.println("-----------------------------------------3");代碼
                優先執行join進來的線程的代碼。
                但是較爲不明顯。
             */
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("-----------------------------------------3");
        System.out.println(t0.isAlive()); //判斷線程是否存活
        System.out.println(t1.isAlive());

    }
}
class RunnalbalImpl2 implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"Runnable多線程運行的代碼");
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);//每次循環睡眠1000ms
                //相當於當前循環每隔1000ms執行一次
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (i % 2 == 0){
                Thread.yield(); //線程讓步 <-- 當i是偶數時
            }
            System.out.println(Thread.currentThread().getName()+"這是Runnable多線程的邏輯代碼" + i);
        }
    }
}
/* 運行結果:
Thread-0Runnable多線程運行的代碼
Thread-1Runnable多線程運行的代碼
-----------------------------------------1
-----------------------------------------2
Thread-0這是Runnable多線程的邏輯代碼0
Thread-0這是Runnable多線程的邏輯代碼1
Thread-0這是Runnable多線程的邏輯代碼2
Thread-0這是Runnable多線程的邏輯代碼3
Thread-0這是Runnable多線程的邏輯代碼4
-----------------------------------------3
false
false
 */

四、線程的生命週期

線程在一個完整的生命週期中通常要經歷如下五種狀態:

  • 新建:當一個Thread類或其子類的對象被聲明應創建時,新生的線程對象處於新建狀態。
  • 就緒:處於新建狀態的線程被start()後,將進入線程隊列等待CPU時間片,此時它已具備了運行的條件。
  • 運行:當就緒的線程被嗲度並獲得處理器資源時,便進入運行狀態,run()方法定義了線程的操作和功能。
  • 阻塞:如果一個線程執行了sleep(睡眠)、suspend(掛起)等方法,失去所佔用資源之後,該線程就從運行狀態進入阻塞狀態。在睡眠時間已到或獲得設備資源後可以重新進入就緒狀態。
  • 死亡:一個運行狀態的線程完成任務或者其他終止條件發生時,該線程就切換到終止狀態。

生命週期圖示:

JavaThread

五、線程的同步

1 案例

例子:同一個賬戶,支付寶轉賬,微信轉賬。現有兩個手機,一個手機開支付寶,另一個開微信,假設現有餘額3000,支付寶和微信同時提款2000。 如果沒有線程同步控制,賬戶就會變爲-1000,這種情況不可以出現。

該例子通過代碼實現如下:

package com.thread;

public class Demo03 {
    public static void main(String[] args) {
        //定義賬戶對象
        Acount a = new Acount();
        User u_wechat = new User(a,2000);
        User u_zhi = new User(a,2000);

        //多線程對象
        Thread wechat = new Thread(u_wechat,"微信");
        Thread zhi = new Thread(u_zhi,"支付寶");

        wechat.start();
        zhi.start();
    }

}
class Acount{
    public static int money = 3000; //全局變量,所有線程共享

    /**
     * 提款,判斷賬戶餘額是否充足
     * 多線程調用這個方法,就有問題,線程共享資源時,一個線程在執行這個方法沒有完畢時,另一個線程又開始執行這個方法。
     * @param m
     */
    public void drawing(int m){
        String name = Thread.currentThread().getName();
        if (money<m){
            System.out.println(name+"操作,賬戶金額不足:" + money);
        }else {
            System.out.println(name + "操作,賬戶原有金額: " + money);
            System.out.println(name + "操作,取款金額: " + m);
            money -= m;
            System.out.println(name + "操作,取款後的金額: " + money);
        }
    }
}

class User implements Runnable{
    Acount acount;
    int money;
    public User(Acount acount, int money){
        this.acount = acount;
        this.money = money;
    }
    @Override
    public void run() {
        acount.drawing(money);
    }
}
/*運行結果:
支付寶操作,賬戶原有金額: 3000
微信操作,賬戶原有金額: 3000
支付寶操作,取款金額: 2000
支付寶操作,取款後的金額: 1000
微信操作,取款金額: 2000
微信操作,取款後的金額: -1000
*/

造成問題的原因: 當多條語句在操作同一個線程共享數據時,一個線程對多條語句只執行了一部分,還沒有執行完,另一個線程參與進來執行。導致共享數據的錯誤。

解決辦法: 對多條操作共享數據的語句,只能讓一個線程都執行完,在執行過程中,其他線程不可以參與執行。

2 synchonized同步鎖!!

通過synchronized同步鎖來完成!

只需在上述案例中Acount類的drawing方法前加上synchronized即可!即:

public synchronized void drawing(int m){}

結果顯示:

{...}
/*
微信支付操作,賬戶原有金額: 3000
微信支付操作,取款金額: 2000
微信支付操作,取款後的金額: 1000
支付寶支付操作,賬戶金額不足:1000
*/

以上問題解決。

synchronized同步鎖注意!!!

  • synchronized放在普通方法前,僅能鎖當前對象(實例),不同對象有不同的鎖。

  • synchronized放在靜態方法前,鎖整個類,所有類對象公用一個鎖。

  • 也可對代碼塊加入同步鎖。實現如下:

    public void drawing2(int m, Acount a){
      //使用this鎖代碼塊是代表當前的對象,如果在其他方法中也有synchronized(this)代碼塊使用的都是同一個同步鎖
      synchronized(this){//表示對整個代碼塊加鎖
        ...
      }
      synchronized(a){ //鎖傳入的對象,並對代碼塊加鎖
        ...
      }; 
    }
    

3 線程的死鎖問題

  • 死鎖

    不同的線程分別佔用對方需要的同步資源不放棄,都在等待對方放棄自己需要的同步資源,就形成了線程的死鎖。

    例:線程a0,需要執行f0;線程a1,需要執行f1;f0和f1都有同步鎖的方法,現在出現一個問題:a0調用f1方法但一直沒有執行完f1,a1調用f0方法但一直沒有執行完f0。導致a0和a1線程都在等待對方釋放方法,而對方都不釋放,這樣就形成了線程的死鎖。

  • 解決辦法

    專門的算法、原則,比如加鎖的順序一致。

    儘量減少同步資源的定義,儘量避免鎖未釋放的場景。

六、線程通信

1 線程通信方法

注:以下三種方法只能用在有同步鎖的方法或代碼塊中!

方法名稱 功能
wait() 另當前線程掛起並放棄CPU、同步資源,使別的線程可以訪問並修改共享資源,而當前線程排隊等候再次對資源的訪問。
notify() 喚醒正在排隊等待同步資源的線程中優先級最高者結束等待。
notifyAll() 喚醒正在排隊等待資源的所有線程結束等待。

現在想要實現以下功能:

如果是微信操作的,先不執行,等待支付寶操作,支付寶操作完成後,微信在繼續操作。

方法案例展示:

package com.thread;

public class Demo03 {
    public static void main(String[] args) {
        //定義賬戶對象
        Acount a = new Acount();
        User u_wechat = new User(a,2000);
        User u_zhi = new User(a,2000);

        //多線程對象
        Thread wechat = new Thread(u_wechat,"微信");
        Thread zhi = new Thread(u_zhi,"支付寶");

        wechat.start();
        zhi.start();
    }

}
class Acount{
    public static int money = 3000; //全局變量,所有線程共享

    public void drawing(int m, Acount a){
        synchronized (a){ //對實例加同步鎖
            String name = Thread.currentThread().getName();

            //如果是微信操作的,先不執行,等待支付寶操作,支付寶操作完成後,微信在繼續操作。
            if(name.equals("微信")){
                try {
                    a.wait();//當前進程進入等待狀態!
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if (name.equals("支付寶")) {
                try {
                    a.notify(); //喚醒當前優先級最高的線程,進入就緒狀態!
//                    a.notifyAll(); //喚醒所有線程,進入就緒狀態!
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (money<m){
                System.out.println(name+"操作,賬戶金額不足:" + money);
            }else {
                System.out.println(name + "操作,賬戶原有金額: " + money);
                System.out.println(name + "操作,取款金額: " + m);
                money -= m;
                System.out.println(name + "操作,取款後的金額: " + money);
            }

        }
    }
}

class User implements Runnable{
    Acount acount;
    int money;
    public User(Acount acount, int money){
        this.acount = acount;
        this.money = money;
    }
    @Override
    public void run() {
        acount.drawing(money,acount);
    }
}
/*運行結果:
支付寶操作,賬戶原有金額: 3000
支付寶操作,取款金額: 2000
支付寶操作,取款後的金額: 1000
微信操作,賬戶金額不足:1000
*/

2 生產者/消費者問題

**問題描述:**生產者將產品交給店員,而消費者從店員處取走產品,店員一次只能持有固定數量的產品(20),如果生產者試圖生產更多的產品,店員會叫生產者停一下;如果店中有空位放產品了在通知生產者繼續生產;如果店中沒有產品了,店員會告訴消費者等一下,如果店中有產品了再通知消費者來取走產品。

此處有兩個問題:

  1. 生產者比消費者更快時,消費者會漏掉一些數據沒有取到。

  2. 消費者比生產者快時,消費者會取相同的數據。

代碼實現:(此處使用兩個匿名內部類實現!)

package com.thread;

/**
 * 生產者與消費者
 */
public class Test3 {
    public static void main(String[] args) {
        //店員
        Clerk c = new Clerk();
        //消費時不生產,生產時不消費

      	//生產者線程
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (c){
                    while (true){ //無限循環代表無限的生產次數
                        if (c.productNum == 0) { //產品數量爲0,開始生產
                            System.out.println("產品數量爲0,開始生產");
                            while (c.productNum < 4){
                                System.out.println("庫存:" + c.productNum++);
                            }
                            System.out.println("產品數爲:" + c.productNum + ",結束生產");

                            c.notify(); //喚醒消費者線程
                        }else {
                            try {
                                c.wait(); //生產者線程等待
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }, "生產者").start();

      	//消費者線程
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (c){
                    while (true) { //無限循環代表無限的生產與消費
                        if (c.productNum != 0) { //產品數爲4,開始消費
                            c.notify(); //喚醒生產者線程
                            System.out.println("產品數爲4,開始消費");
                            while (c.productNum > 0) {
                                System.out.println("庫存:" + c.productNum--);
                            }
                            System.out.println("產品數爲:" + c.productNum + ",結束消費");
                        } else {
                            try {
                                c.wait(); //消費者線程等待
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }, "消費者").start();
    }
}

class Clerk{
    public static int productNum;
}

寫在最後

行百里者半九十!

To Demut and Dottie!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章