1. 程式人生 > 其它 >Java多執行緒04:執行緒優先順序、守護執行緒和執行緒同步

Java多執行緒04:執行緒優先順序、守護執行緒和執行緒同步

Java多執行緒04:執行緒優先順序、守護執行緒和執行緒同步

執行緒優先順序

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

例項:

package com.lurenj.thread;

public class TestPriority {

    public static void main(String[] args) {

        MyPriority myPriority = new MyPriority();

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

        //設定優先順序再啟動
        t1.start();

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

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

        t4.setPriority(10);
        t4.start();

        t5.setPriority(3);
        t5.start();

        t6.setPriority(6);
        t6.start();
        //主執行緒預設優先順序
        System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());

    }

}

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

守護(daemon)執行緒

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

例項

package com.lurenj.thread;

public class TestDaemon {
    public static void main(String[] args) {
        God god = new God();
        You2 you2 = new You2();

        Thread thread = new Thread(god);
        thread.setDaemon(true);//預設是false表示是使用者執行緒,正常的執行緒都是使用者執行緒
        thread.start();//守護執行緒啟動

        new Thread(you2).start();


    }
}



//God

class God implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("保佑著你~");
        }
    }
}


//你
class You2 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("一生都開心的活著~");
        }
        System.out.println("========goodbye!=========");
    }
}

執行緒同步機制

  • 併發:同一個物件被多個執行緒同時操作
  • 執行緒同步:處理多執行緒問題時,多個執行緒訪問同一個物件(併發) ,並且某些執行緒還想修改這個物件,這時候我們就需要執行緒同步,執行緒同步其實就是一種等待機制,多個需要同時訪問此物件的執行緒進入這個物件的等待池 形成佇列,等待前面執行緒使用完畢,下一個執行緒再使用。
  • 執行緒同步形成條件:佇列+鎖
    • 由於同一程序的多個執行緒共享同一塊儲存空間,在帶來方便的同時,也帶來了訪問衝突問題,為了保證資料在方法中被訪問時的正確性,在訪問時加入鎖機制(synchronized) ,當一個執行緒獲得物件的排它鎖,獨佔資源,其他執行緒必須等待,使用後釋放鎖即可
  • 執行緒同步存在的問題:
    • 一個執行緒持有鎖會導致其他所有需要此鎖的執行緒掛起;
    • 在多執行緒競爭下,加鎖、釋放鎖會導致比較多的上下文切換和排程延遲,引起效能問題;
    • 如果一個優先順序高的執行緒等待一個優先順序低的執行緒釋放鎖會導致優先順序倒置,引起效能問題;

三大不安全案例

案例一:買票不安全,會出現重複或-1

package com.lurenj.syn;

//不安全的買票
//執行緒不安全,有負數和重複購買
public class UnsafeTicket {
    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();

        new Thread(station,"kubi").start();
        new Thread(station,"niubi").start();
        new Thread(station,"huangniu").start();
    }

}

class BuyTicket implements Runnable{

    //車票
    private int ticketNums = 10;
    private boolean flag = true;//外部停止
    @Override
    public void run() {
        while (flag){
            buy();
        }

    }

    private void buy(){
        //判斷是否有票
        if (ticketNums <= 0){
            flag = false;
            return;
        }
        //模擬延時
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //買票
        System.out.println(Thread.currentThread().getName() + "拿到了" + ticketNums--);
    }
}

案例二:陣列不安全,輸出錯誤

package com.lurenj.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 < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        System.out.println(list.size());
    }
}

案例三:銀行取錢不安全,出現負數或者取出總額大於存款

package com.lurenj.syn;

import com.lurenj.oop.demo05.A;

//不安全的取錢
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,"女友");

        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(this.getName() + "錢不夠,取不了");
            return;
        }

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

        account.money = account.money - drawingMoney;
        nowMoney += drawingMoney;
        System.out.println(account.name + "餘額為:" + account.money);
        //Thread.currentThread().getName() = this.getName(),因為該類繼承了Thread類,getName是其中的方法
        System.out.println(this.getName() + "手裡的錢" + nowMoney);
    }
}

同步方法

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

    同步方法:public synchronized void method(int args){}

  • synchronized方法控制對”物件“的訪問,每個物件對應一把鎖,每個synchronized方法都必須獲得呼叫該方法的物件的鎖才能執行,否則執行緒會阻塞,方法一旦執行,就獨佔該鎖,直到該方法返回才釋放鎖,後面被阻塞的執行緒才能獲得這個鎖,繼續執行。

    缺陷:若將一個大的方法宣告為synchronized將會影響效率;方法裡面需要修改的內容才需要鎖,鎖的太多,浪費資源

同步塊

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

例項一:解決買票不安全

package com.lurenj.syn;

//不安全的買票
//執行緒不安全,有負數和重複購買
public class UnsafeTicket {
    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();

        new Thread(station,"kubi").start();
        new Thread(station,"niubi").start();
        new Thread(station,"huangniu").start();
    }

}

class BuyTicket implements Runnable{

    //車票
    private int ticketNums = 10;
    private boolean flag = true;//外部停止
    @Override
    public void run() {
        while (flag){
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    //synchronized 同步方法,鎖的是this
    private synchronized void buy() throws InterruptedException {//在此處新增synchronized,就解決了
        //判斷是否有票
        if (ticketNums <= 0){
            flag = false;
            return;
        }
        //模擬延時
            Thread.sleep(100);
        //買票
        System.out.println(Thread.currentThread().getName() + "拿到了" + ticketNums--);
    }
}

例項二:解決銀行取錢不安全

package com.lurenj.syn;

import com.lurenj.oop.demo05.A;

//不安全的取錢
public class UnsafeBank {
    public static void main(String[] args) {
        //賬戶
        Account account = new Account(1000,"結婚基金");

        Drawing you = new Drawing(account,50,"你");
        Drawing girlfriend = new Drawing(account,100,"女友");

        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;
    }

    //取錢
    //synchronized預設鎖的是this,這裡預設鎖是<銀行:模擬取款>,而我們實際需要鎖的是<賬戶>
    @Override
    public void run() {

        synchronized (account){

            //判斷是否有錢
            if (account.money - drawingMoney < 0){
                System.out.println(this.getName() + "錢不夠,取不了");
                return;
            }

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

            account.money = account.money - drawingMoney;
            nowMoney += drawingMoney;
            System.out.println(account.name + "餘額為:" + account.money);
            //Thread.currentThread().getName() = this.getName(),因為該類繼承了Thread類,getName是其中的方法
            System.out.println(this.getName() + "手裡的錢" + nowMoney);
        }
    }
}

案例三:解決陣列不安全問題

package com.lurenj.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 < 10000; i++) {
            new Thread(()->{
                synchronized (list){//通過鎖住list物件來解決
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}