1. 程式人生 > 其它 >多執行緒-同步方法及同步塊(修改三大不安全案例)

多執行緒-同步方法及同步塊(修改三大不安全案例)

同步方法:只能鎖類本身

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

      同步方法:public synchronzed void method(int args){}
   synchronzed方法控制對”物件“的訪問,每個物件對應一把鎖,每個synchronzed方法都必須獲得呼叫該方法物件的鎖才能執行,否則執行緒會阻塞方法,方法一旦執行,就獨佔該鎖,直到方法返回才釋放鎖,後面被阻塞的執行緒才能獲得這個鎖,繼續執行。

      缺點:若將一個大的方法申明為synchronzed將會影響效率
同步塊:當一個大方法中,只有一小部程式碼需要用同步加鎖時,我們可以使用同步塊,如:當方法中包含的程式碼有一部程式碼只是讓你去讀取的程式碼,另一部份程式碼是用來修改東西的,這只是有修改東西的那個程式碼需要同步,如果不同步當多人同時修改一個內容時,就會出現問題,那這個時候,我們只需要把同步程式碼塊寫到對應程式碼只即可。

同步塊:synchronized((Obj){}:可以鎖任何物件
Obj稱之為同步監視器:Obj的物件一定是需要增刪改的物件

  Obj可以是任何物件,但是推薦使用共享資源作為同步監視器,也就是需要修改的那個物件作為監視器

  同步方法中無需指定同步監視器,因為方法的同步監視器就是this,就是這個物件本身,或者class

同步監視器的執行過程:

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

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

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

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

 

//安全的買票:使用同步方法
//使用同步方法,讓他安全
public class Main{
    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; public void run(){ //買票 while(flag){ try {//下面丟擲異常上面就要處理 buy(); } catch(Exception e) { System.out.println("異常"); } } } //買票的方法 在買票的方法中加入同步方法關鍵字,讓他變成同步方法即可 private synchronized void buy()throws InterruptedException{ //判斷是否有票 if (ticketNums<=0){ flag=false; return; } //模擬延時 Thread.sleep(100); System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--); } }
//安全的取錢:使用同步塊
//兩個人去銀行取錢,賬戶
//sleep():可以放大問題的發生性
public class Main {
    public static void main(String[] args) {
    //賬戶
    Account acc=new Account(1000,"理財基金");
    //建立銀行物件,構造器中,傳入賬戶及取錢金額及名字
    Drawing you=new Drawing(acc,50,"我");
    Drawing gir=new Drawing(acc,100,"他");
    you.start();
    gir.start();
    
    }
}
//賬戶
class Account{
    int money;//餘額
    String name;//卡名
    
    public Account(){}

    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);//獲取執行緒的名字,因為繼承了Thread是可以用父類方法來獲取的 
        this.account=account;
        this.drawingMoney=drawingMoney;
        
    }
    
    //取錢:重寫run,注意:如果我們直接用synchronized 鎖run方法,那依舊會有負數,因為run的this類是銀行類,但是我們增刪改查的操作銀行是針對賬戶的,所以去鎖銀行,是鎖不到的,所以這個方法,我們不能用同步方法去寫,而是用同步塊
    //synchronized預設鎖的物件是:this,方法也就是他本身
    public  void run(){
       
       synchronized(account){//使用同步塊,為什麼account,可以當做監視器使用呢,我們要鎖的是會變化的量,需要增刪改的物件
       
        //判斷有沒有錢
        if (account.money-drawingMoney<0){//如果賬戶裡的錢減去要取的錢小於0,就是不夠
        System.out.println(Thread.currentThread().getName()+":你的餘額不足");
        return;
        } 
        try {
             //當如果賬戶有錢,我們進來後,都停一秒鐘,這樣就會讓賬戶出現負數        //為什麼出現問題呢,因為這兩個賬戶的記憶體都不一致的,當他們看到100時,是用時看到的,所以以為都能取完,這就導致了執行緒的不安全,只有讓一個執行緒先執行完,再讓其他執行緒進行執行,這樣才會安全
        Thread.sleep(1000);
        } catch(InterruptedException e) {
            System.out.println("有異常");
        } 
        
   
        //卡內餘額=餘額-你取的錢
        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);
        
       }
      
       
        
    }
    
}
//執行緒不安全的集合
import java.util.ArrayList;
import java.util.list;

public class Main {
    public static void main(String[] args) {
        List<String>list=new ArrayList<String>();
        for (int i=0;i<1000 ;i++ ){
            new Thread(()->{//建立1000個執行緒
            //使用同步塊鎖變化的量
            synchronized(list){
                 list.add(Thread.currentThread().getName());//把執行緒的名字新增到集合中
            }
               
            }).start();
        } 
        try {
             Thread.sleep(1000);
        } catch(InterruptedException e) {
            System.out.println("no");
        } 
       
        System.out.println(list.sixe());//輸出一下集合的大小,但是輸出正常是不夠,因為有時候會出來兩個元素放在一個位置然後會覆蓋掉
    }
}