Java執行緒同步(一)synchronized方法與方法塊
阿新 • • 發佈:2021-12-07
執行緒同步
多個執行緒操作同一個資源
- 併發:同一個物件被多個執行緒同時操作
- 顯示生活中,我們會遇到“同一個醫院,多個人都想使用”的問題,比如,食堂排隊打飯,每個人都想吃飯,嘴甜飯的解決方法就是,排隊,一個一個來
- 處理多執行緒問題時,多個執行緒訪問同一個物件,並且某些執行緒還想修改這個物件,這時候我們就需要執行緒同步。執行緒同步其實就是一種等待機制,多個需要同時訪問此物件的執行緒,進入這個物件等待池形成佇列,等待前面執行緒使用完畢,下一個執行緒再使用。(併發!!)
佇列和鎖
當一下有三個人同時打飯,就會造成混亂。所以需要鎖這個機制。為了確保安全
執行緒同步
- 由於同一個程序的多個執行緒共享同一塊儲存空間,在帶來方便的同時,也帶來了訪問的衝突問題,為了保證資料在方法中被訪問的正確性,在訪問時加入鎖機制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了