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