Java線程同步(一)synchronized方法與方法塊


線程同步

多個線程操作同一個資源

  • 並發:同一個對象多個線程同時操作

image

  • 顯示生活中,我們會遇到“同一個醫院,多個人都想使用”的問題,比如,食堂排隊打飯,每個人都想吃飯,嘴甜飯的解決方法就是,排隊,一個一個來

image

  • 處理多線程問題時,多個線程訪問同一個對象,並且某些線程還想修改這個對象,這時候我們就需要線程同步。線程同步其實就是一種等待機制,多個需要同時訪問此對象的線程,進入這個對象等待池形成隊列,等待前面線程使用完畢,下一個線程再使用。(並發!!)

隊列和鎖

image

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

image

線程同步

  • 由於同一個進程的多個線程共享同一塊存儲空間,在帶來方便的同時,也帶來了訪問的沖突問題,為了保證數據在方法中被訪問的正確性,在訪問時加入鎖機制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--);
    }

}

image

有負一,線程不安全有負數

  • 為什么會出現負數?

  • 當票還剩下一張的時候,線程同時取票,就會造成出現-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);

    }
}

image

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

image

睡眠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());
    }
}


image

同步方法

  • 由於我們可以通過private關鍵字來保證數據對象只能被方法訪問,所以我們只需要針對方法體術一套機制,這套機制就是synchronized關鍵字,它包括兩種用法:synchronized發明回復和synchronized塊

image

  • synchronized方法控對“對象”的訪問,每一個對象對應一把鎖,每個synchronized方法都必須獲得調用該方法的對象的鎖才能執行,否則線程會阻塞,方法一旦執行,就獨占該鎖,知道該方法返回才釋放鎖,后面被阻塞的線程才能獲得這個鎖,繼續執行

image

同步方法弊端

image

同步塊

  • 同步塊: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--);
    }

}

image

方法前面加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鎖的是並行的,我們需要鎖得是賬戶

image

結婚基金改為1000

image

list使用synchronized代碼塊包起來

線程就能跑到10000了

image


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM