1. 程式人生 > 其它 >Java執行緒同步(一)synchronized方法與方法塊

Java執行緒同步(一)synchronized方法與方法塊

執行緒同步

多個執行緒操作同一個資源

  • 併發:同一個物件多個執行緒同時操作
  • 顯示生活中,我們會遇到“同一個醫院,多個人都想使用”的問題,比如,食堂排隊打飯,每個人都想吃飯,嘴甜飯的解決方法就是,排隊,一個一個來
  • 處理多執行緒問題時,多個執行緒訪問同一個物件,並且某些執行緒還想修改這個物件,這時候我們就需要執行緒同步。執行緒同步其實就是一種等待機制,多個需要同時訪問此物件的執行緒,進入這個物件等待池形成佇列,等待前面執行緒使用完畢,下一個執行緒再使用。(併發!!)

佇列和鎖

當一下有三個人同時打飯,就會造成混亂。所以需要鎖這個機制。為了確保安全

執行緒同步

  • 由於同一個程序的多個執行緒共享同一塊儲存空間,在帶來方便的同時,也帶來了訪問的衝突問題,為了保證資料在方法中被訪問的正確性,在訪問時加入鎖機制synchronized
    ,當一個執行緒獲得物件的排他鎖,獨佔資源,其他執行緒必須等待,使用後釋放鎖即可,存在一下問題:
    • 一個執行緒持有鎖會導致其他所有需要此鎖的執行緒掛起;
    • 在多執行緒競爭下,加鎖,釋放鎖會導致比較多的上下文切換和排程延時,引起效能問題;
    • 如果一個優先順序高的執行緒等待一個優先順序低的執行緒釋放鎖,會導致優先順序倒置問題,引起效能問題。
package com.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){
            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--);
    }

}

有負一,執行緒不安全有負數

  • 為什麼會出現負數?

  • 當票還剩下一張的時候,執行緒同時取票,就會造成出現-11的狀況。

package com.syn;

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

        you.start();
        grilFriend.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.syn;

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

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

睡眠3s後

package com.syn;

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

//執行緒不安全的集合
public class UnsafeList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; 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(Obj){}

  • Obj稱之為同步監視器

    • Obj可以是任何物件,但是推薦使用共享資源作為同步監視器
    • 同步方法中無需指定同步監視器,因為同步方法監視器就是this,就是這個物件本身,活著是class[反射中講解]
  • 同步監視器的執行過程

    1.第一個執行緒訪問,鎖定同步監視器,執行其中程式碼.

    2.第二個執行緒訪問,發現同步監視器被鎖定,無法訪問.

    3.第一個執行緒訪問完畢,解鎖同步監視器.

    4.第二個執行緒訪問,發現同步監視器沒有鎖,然後鎖定並訪問.

package com.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();
            }
        }
    }

    //synchronized 同步方法,鎖的是this
    private synchronized void buy() throws InterruptedException {
        //判斷是否有票
        if(ticketNums<=0){
            flag = false;
            return;
        }
        //模擬延時

        Thread.sleep(100);

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

}

方法前面加synchronized

發現負數消失了

取錢的例子

package com.syn;

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

        you.start();
        grilFriend.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(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);


        }


    }
}

鎖this鎖的是頻行,我們需要鎖得是賬戶

結婚積極改為1000

list使用synchronized程式碼塊包起來

執行緒就能跑到10000了