多執行緒Day03

东方语虑發表於2024-10-02

執行緒優先順序

  • Java提供一個執行緒排程器來監控程式中啟動後進入就緒狀態的所有執行緒,執行緒排程器按照優先順序決定應該排程哪個執行緒來執行
  • 執行緒的優先順序用數字表示,範圍從1~10
    • Thread.MIN_PRIORITY = 1;
    • Thread.MAX_PRIORITY = 10;
    • Thread.NORM_PRIORITY = 5;
  • 使用以下方式改變或獲取優先順序
    • getPriority.setPriority(int xxx)
package com.dongfangyulv.state;

//測試執行緒的優先順序
public class TestPriority {
    public static void main(String[] args) {
        //主執行緒預設優先順序
        System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority());

        MyPriority myPriority = new MyPriority();

        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);

        //先設定優先順序,再啟動,範圍從1~10,預設為5
        t1.start();

        t2.setPriority(1);
        t2.start();

        t3.setPriority(4);
        t3.start();

        t4.setPriority(Thread.MAX_PRIORITY);//MAX_PRIORITY = 10
        t4.start();
    }
}

class MyPriority implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority());
    }
}

守護執行緒

  • 執行緒分為使用者執行緒和守護執行緒
  • 虛擬機器必須確保使用者執行緒執行完畢
  • 虛擬機器不用等待守護執行緒執行完畢
  • 如:後臺記錄操作日誌、監控記憶體、垃圾回收等待
package com.dongfangyulv.state;

//測試守護執行緒
//上帝守護你
public class TestDaemon {
    public static void main(String[] args) {
        God god = new God();
        You you = new You();
        
        Thread thread = new Thread(god);
        thread.setDaemon(true);//預設為false使用者執行緒
        
        thread.start();//上帝守護執行緒啟動
        
        new Thread(you).start();//你  使用者執行緒啟動
    }
}

//上帝
class God implements Runnable {
    @Override
    public void run() {
        System.out.println("deamon");
    }
}

//你
class You implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("live");
        }
        System.out.println("bye world");
    }
}

執行緒同步機制

併發:同一個物件被多個執行緒同時操作

處理多執行緒問題時,多個執行緒訪問同一個物件,並且某些執行緒還想修改這個物件,這時候我們就需要執行緒同步。執行緒同步其實就是一種等待機制,多個需要同時訪問此物件的執行緒進入這個物件的等待池形成佇列,等待前面執行緒使用完畢,下一個執行緒再使用

鎖機制 (synchronized) :當一個執行緒獲得物件的排它鎖,獨佔資源,其他執行緒必須等待,使用後釋放鎖即可,存在以下問題:

  • 一個執行緒持有鎖會導致其他所有需要此鎖的執行緒掛起
  • 在多執行緒競爭下,加鎖,釋放鎖會導致比較多的上下文切換排程延時,引起效能問題
  • 如果一個優先順序高的執行緒等待一個優先順序低的執行緒釋放鎖,會導致優先順序倒置,引起效能問題

三大不安全案例

package com.dongfangyulv.syn;

//不安全的買票
//執行緒不安全
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();

        new Thread(station,"甲").start();
        new Thread(station,"乙").start();
        new Thread(station,"丙").start();
    }
}

class BuyTicket implements Runnable {

    //票
    private int ticketNums = 10;
    boolean flag = true;//外部停止方式

    @Override
    public void run() {
        //買票
        while(flag) {
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void buy() throws InterruptedException{
        //判斷是否有票
        if (ticketNums <= 0) {
            flag = false;
            return;
        }
        //模擬延時
        Thread.sleep(100);
        //買票
        System.out.println(Thread.currentThread().getName() + "拿到" + ticketNums--);
    }
}
package com.dongfangyulv.syn;

//不安全的取錢
//兩個人去銀行取錢,賬戶
public class UnsafeBank {
    public static void main(String[] args) {
        //賬戶
        Account account = new Account(100,"結婚基金");
        Drawing you = new Drawing(account,50,"你");
        Drawing girlFriend = new Drawing(account,100,"girlFriend");

        you.start();
        girlFriend.start();
    }
}

//賬戶
class Account {
    int money;//餘額
    String name;//卡名

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}

//銀行:模擬取款
class Drawing extends Thread {
    Account account;//賬戶
    //取了多少錢
    int drawingMoney;
    //手裡有多少錢
    int nowMoney;

    public Drawing(Account account, int drawingMoney, String name) {
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    //取錢
    @Override
    public void run() {
        //判斷有沒有錢
        if (account.money - drawingMoney < 0) {
            System.out.println(Thread.currentThread().getName() + "錢不夠,取不了");
            return;
        }

        //sleep可以放大問題的發生性
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //卡內餘額=餘額-取的錢
        account.money = account.money - drawingMoney;
        //手裡的錢
        nowMoney = nowMoney + drawingMoney;

        System.out.println(account.name + "餘額為:" + account.money);
        //Thread.currentThread().getName() = this.getName()
        System.out.println(this.getName() + "手裡的錢" + nowMoney);
    }
}

package com.dongfangyulv.syn;

import java.util.ArrayList;
import java.util.List;

//執行緒不安全的集合
public class UnsafeList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        for (int i = 0; i < 1000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

同步方法及同步塊

  • 由於我們可以透過private關鍵字來保證資料物件只能被方法訪問,所以我們只需要針對方法提出一套機制,這套機制就是synchronized關鍵字它包括:synchronized方法synchronized塊

同步方法

  • synchronized方法控制對“物件”的訪問,每個物件對應一把鎖,每個synchronized方法都必須獲得呼叫該方法的物件的鎖才能執行,否則執行緒會阻塞,方法一旦執行,就獨佔該鎖,直到該方法返回才釋放鎖,後面被阻塞的執行緒才能獲得這個鎖,繼續執行
  • 若將一個大方法申明為synchronized將會影響效率
  • 方法裡面需要修改的資源才需要鎖,鎖的太多,浪費資源

同步塊

  • synchronized (Obj)
  • Obj稱之為同步監視器
    • Obj可以是任何物件,但是推薦使用共享資源作為同步監視器
    • 同步方法中無需指定同步監視器,因為同步方法的同步監視器就是this這個物件本身,或者是class
  • 同步監視器的執行過程
    1. 第一個執行緒訪問,鎖定同步監視器,執行其中程式碼
    2. 第二個執行緒訪問,發現同步監視器被鎖定,無法訪問
    3. 第一個執行緒訪問完畢,解鎖同步監視器
    4. 第二個執行緒訪問,發現同步監視器沒有鎖,然後鎖定並訪問

CopyOnWriArrayList

package com.dongfangyulv.syn;

import java.util.concurrent.CopyOnWriteArrayList;

//測試JUC安全型別的集合
public class TestJUC {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
        for (int i = 0; i < 1000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

相關文章