一、線程同步
多個線程共享相同的數(shù)據(jù)或資源旨剥,就會出現(xiàn)多個線程爭搶一個資源的情況咧欣。這時就容易造成數(shù)據(jù)的非預(yù)期(錯誤)處理,是線程不安全的轨帜。
Java中對線程同步的支持魄咕,最常見的方式是添加synchronized同步鎖。
- 給方法加鎖蚌父,稱為同步方法
- 非靜態(tài)方法加鎖哮兰,鎖的是方法所屬的對象,即誰調(diào)用此方法就是誰苟弛。new不同對象時喝滞,因為鎖的對象不同,則不同步嗡午。
- 靜態(tài)方法加鎖囤躁,鎖的是方法所屬的類,無論new多少都是一定具有同步效果的。
-synchronized塊狸演,給方法某一部分加鎖言蛇,提高同步效率。this指的是調(diào)用此方法的對象宵距。如下例所示腊尚,this指的是調(diào)用run的線程 - synchronized修飾不同方法或者代碼塊時,若多個線程看到的對象相同满哪,則這些方法間具有互斥性婿斥,不能并發(fā)運行。例如一個類中A/B方法上鎖哨鸭,不同線程分別調(diào)用A和B民宿,需要在A執(zhí)行完,B方可執(zhí)行
- 集合工具類Collections可將線程非安全的集合轉(zhuǎn)為線程安全的像鸡。Ar'ra'y'list活鹰、linkedlist、hashset只估、hashmap都是線程不安全的志群。
舉例如下:
//售票系統(tǒng)
public class SaleService {
private String ticketName;//票名
private int totalCount;//總票數(shù)
private int remaining;//剩余票數(shù)
SaleService(String ticketName,int totalCount){
this.ticketName = ticketName;
this.totalCount = totalCount;
this.remaining = totalCount;
}
public int synchronized sale(int ticketNum){
if(remaining>0){
remaining -= ticketNum;
try {
Thread.sleep(100);//暫停0.1秒,模擬真實系統(tǒng)中復(fù)雜計算所用的時間
} catch (InterruptedException e) {
e.printStackTrace();
}
if(remaining>=0){
return remaining;
}else{
remaining += ticketNum;
return -1;
}
}
return -1;
}
public String getTicketName() {
return ticketName;
}
public int getRemaining() {
return remaining;
}
}
//售票窗
public class TicketSaler implements Runnable{
private String name;
private SaleService saleService;
TicketSaler(String name,SaleService saleService){
this.name = name;
this.saleService = saleService;
}
public void run() {
while(saleService.getRemaining()>0){
synchronized (this) {
System.out.println(Thread.currentThread().getName()+"出售第"+saleService.getRemaining()+"票");
int remaining = saleService.sale(3);
if(remaining>=0){
System.out.println("出票成功蛔钙!剩余"+remaining+"張票");
}else{
System.out.println("出票失斝吭啤!剩余"+saleService.getRemaining()+"張票");
}
}
}
}
}
public static void main(String[] args) {
SaleService service = new SaleService("廣州南-深圳", 50);
TicketSaler saler = new TicketSaler("售票窗口",service);
Thread threads[] = new Thread[5];
for(int i=0;i<threads.length;i++){
threads[i] = new Thread(saler, "窗口"+i);
threads[i].start();
}
}
輸出如下:
image.png
如果去掉run方法中的鎖吁脱,則會發(fā)生資源安全問題桑涎!hava a try!
注意:只有sale方法的鎖豫喧,沒有run里的鎖石洗,是可以將sale方法鎖住,保證線程不能同時調(diào)用sale方法紧显,但有可能引起方法執(zhí)行完成后代碼塊的同步。比如窗口1執(zhí)行完sale缕棵,余票為2孵班,此時窗口2正在執(zhí)行,還未執(zhí)行完成招驴,窗口1執(zhí)行輸出的時候remaining可能剛好是-1了篙程,引起輸出錯誤。