Linux伺服器程式規範化
阿新 • • 發佈:2021-12-21
執行緒同步
併發:多個執行緒訪問同一個物件 例:上萬人同時搶100張票
處理多執行緒問題時,多個執行緒訪問同一個物件,並且某些執行緒還想修改這個物件。這時候需要執行緒同步,
執行緒同步其實就是一種等待機制,多個需要同時訪問此物件的執行緒進入這個物件的等待池形成佇列,等待前面執行緒使用完畢,下一個執行緒再使用
執行緒同步:佇列+鎖(synchronized)
執行緒同步存在的問題(損失效能):
- 一個執行緒持有鎖會導致其他所有需要此鎖的執行緒掛起
- 在多執行緒競爭下,加鎖,釋放鎖會導致比較多的上下文切換和排程延時,引起效能問題
- 如果一個優先順序高的執行緒等待一個優先順序低的執行緒釋放鎖,會導致優先順序倒置,引起效能問題
package com.yuanyu.syn; //不安全的買票 public class UnsafeBuyTicket { public static void main(String[] args) { BuyTicket buyTicket = new BuyTicket(); new Thread(buyTicket,"小紅").start(); new Thread(buyTicket,"小黑").start(); new Thread(buyTicket,"小白").start(); } } class BuyTicket implements Runnable{ private int ticketNums=10; private 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--); } }
程式執行結果:
每個執行緒在自己的工作記憶體互動,記憶體控制不當會造成資料不一致
package com.yuanyu.syn; //不安全的取錢 //兩個人去銀行取錢,賬戶 public class UnsafeBank { public static void main(String[] args) { Account account = new Account(100,"小金庫"); Drawing d1 = new Drawing(account,50,"I"); Drawing d2 = new Drawing(account,100,"You"); d1.start(); d2.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; //手裡錢數 @Override public void run() { if (account.money-drawingMoney<0){ System.out.println(Thread.currentThread().getName()+"錢不夠了,取不了"); return; } account.money=account.money-drawingMoney; //賬戶餘額 nowMoney=nowMoney+drawingMoney; //手裡的錢 System.out.println(account.name+"餘額為:"+account.money); System.out.println(this.getName()+"手裡的錢為:"+nowMoney); //this.getName()=Thread.currentThread().getName() } public Drawing(Account account, int drawingMoney, String name){ super(name); this.account=account; this.drawingMoney=drawingMoney; } }
程式執行結果:
You錢不夠了,取不了
小金庫餘額為:50
I手裡的錢為:50
Thread.sleep() 延時可以放大問題的發生性
package com.yuanyu.syn;
import java.util.ArrayList;
//執行緒不安全的集合
public class UnsafeTest {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
} ).start();
}
//延時可以放大問題的發生性
// try {
// Thread.sleep(3000);
// } catch (InterruptedException e) { //1000
// e.printStackTrace();
// }
System.out.println(list.size()); //994
}
}
併發的解決方法:執行緒同步
通過synchronized關鍵字控制“物件”的訪問,每個物件對應一把鎖,每個synchronized方法都必須獲得呼叫該方法的物件的鎖才能執行,否則執行緒會阻塞,方法一旦執行,就獨佔該鎖,直到該方法返回才釋放鎖,後面被阻塞的執行緒才能獲得這個鎖,繼續執行
【缺陷】:若將一個大的方法宣告為synchronized將會影響效率
方法裡面需要修改的內容才需要鎖,鎖的太多會浪費資源
package com.yuanyu.syn;
//安全的買票
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"小紅").start();
new Thread(buyTicket,"小黑").start();
new Thread(buyTicket,"小白").start();
}
}
class BuyTicket implements Runnable{
private int ticketNums=10;
private boolean flag=true;
@Override
public void run() {
//買票
while (flag){
buy();
}
}
//synchronized同步方法,鎖的是this
private synchronized void buy(){
if (ticketNums<=0){
flag=false;
return;
}
//模擬延時
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"買到了"+ticketNums--);
}
}
程式執行結果:
package com.yuanyu.syn;
//安全的取錢
//兩個人去銀行取錢,賬戶
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(100,"小金庫");
Drawing d1 = new Drawing(account,50,"I");
Drawing d2 = new Drawing(account,100,"You");
d1.start();
d2.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; //手裡錢數
@Override
public synchronized void run() {
synchronized (account) {
if (account.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+"錢不夠了,取不了");
return;
}
account.money=account.money-drawingMoney; //賬戶餘額
nowMoney=nowMoney+drawingMoney; //手裡的錢
System.out.println(account.name+"餘額為:"+account.money);
System.out.println(this.getName()+"手裡的錢為:"+nowMoney); //this.getName()=Thread.currentThread().getName()
}
}
public Drawing(Account account, int drawingMoney, String name){
super(name);
this.account=account;
this.drawingMoney=drawingMoney;
}
}
鎖的物件一定是個變化的量,需要增刪改的物件
以上程式碼示例變化的不是銀行,而是賬戶,因此synchronized應該鎖賬戶account
package com.yuanyu.syn;
import java.util.ArrayList;
//執行緒安全的集合
public class UnsafeTest {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
synchronized (list) {
list.add(Thread.currentThread().getName());
}
} ).start();
}
//延時可以放大問題的發生性
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
10000
synchronized(Obj){}塊 (Alt+Ctrl+T)
Obj稱之為同步監控器
Obj可以是任何物件,但是推薦使用共享資源作為同步監控器
同步方法中無需指定同步監控器,因為同步方法的同步監控器就是this,就是這個物件本身或者是class
同步監控器的執行過程
- 第一個執行緒訪問,鎖定同步監控器,執行其中程式碼
- 第二個執行緒訪問,發現同步監控器被鎖定,無法訪問
- 第一個執行緒訪問完畢,鎖定同步監控器
- 第二個執行緒訪問,發現同步監控器沒有鎖,然後鎖定訪問