多執行緒-同步方法及同步塊(修改三大不安全案例)
阿新 • • 發佈:2022-05-07
同步方法:只能鎖類本身 由於我們可以通過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());//輸出一下集合的大小,但是輸出正常是不夠,因為有時候會出來兩個元素放在一個位置然後會覆蓋掉 } }