146.synchronized同步方法與塊

雲疏不知數發表於2020-09-30

將之前寫的執行緒不安全的web12306模擬搶票程式碼,引入執行緒安全 1的處理辦法

package zy.thread;


public class unsafeWeb12306 implements Runnable{
	private boolean flag = true;
	private int ticketNums = 10;
	public void run() {
		while(flag) {
			get();
		}
	}
	
	public void get() {
		if(ticketNums <= 0) {
			flag = false;
			return;
		}
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + "-->" + ticketNums--);
	}
	
	public static void main(String[] args) {
		unsafeWeb12306 w1 = new unsafeWeb12306();
		System.out.println(Thread.currentThread().getName()); //main
		
		new Thread(w1, "碼蓄").start();
		new Thread(w1, "碼農").start();
		new Thread(w1, "碼蝗").start();
	}
}

以上程式碼的執行結果是,同一張票會被多人獲得,沒有票的時候也能搶到票,是因為對臨界資源票的訪問沒有加鎖

146.synchronized同步方法與塊

出現負數情況的原因是,最後一張票的時候,多個執行緒同時跑到搶票的步驟,也就是說控制條件沒有限制住。同一張票被多個人搶到的情況是因為每個執行緒都有自己獨立的工作空間,在第一個執行緒將更改完的資料寫回到主存之前,其它執行緒已經將主存的資料拷貝到自己的工作空間,因此就會出現同一張票被多個執行緒搶到的情況,總結就是臨界資源的訪問和執行緒之間同步的問題。

使用synchronized方法,使搶票程式碼執行緒安全 2

package zy.thread;


public class safeWeb12306 implements Runnable{
	private boolean flag = true;
	private int ticketNums = 10;
	public void run() {
		while(flag) {
			get();
		}
	}
	
	public synchronized void get() {
		if(ticketNums <= 0) {
			flag = false;
			return;
		}
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + "-->" + ticketNums--);
	}
	
	public static void main(String[] args) {
		safeWeb12306 w1 = new safeWeb12306();
		System.out.println(Thread.currentThread().getName()); //main
		
		new Thread(w1, "碼蓄").start();
		new Thread(w1, "碼農").start();
		new Thread(w1, "碼蝗").start();
	}
}

synchronized修飾的是safeWeb12306類的成員方法,執行緒執行的時候鎖的就是this物件,this的資料成員的修改都是互斥的

執行緒不安的銀行存取款程式碼

package zy.thread;

public class unsafeAccount {
	public static void main(String[] args) {
		Account a = new Account(100, "原麥山丘");
		drawing boy = new drawing(a, 60, "boyDrawing");
		drawing girl = new drawing(a, 80, "girlDrawing");
		boy.start();
		girl.start();
	}
}

//賬戶
class Account{
	int money;
	String name;
	public Account(int money, String name) {
		super();
		this.money = money;
		this.name = name;
	}
}

//取款的業務類
class drawing extends Thread{
	Account account;
	int drawingMoney;
	int packetTotal;
	
	public drawing(Account account, int drawingMoney, String name) {
		super(name);
		this.account = account;
		this.drawingMoney = drawingMoney;
	}
	
	public void run() {
		get();
	}
	
	public void get() {
		if(account.money < drawingMoney)
			return;
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		account.money -= drawingMoney;
		packetTotal += drawingMoney;
		System.out.println(this.getName() + "賬戶餘額" + account.money);
		System.out.println(this.getName() + "口袋餘額" + packetTotal);
	}
}

即使加了如下的判斷,在網路延時的情況下,判斷無法發揮作用,最終執行緒不安全,最終賬戶剩餘餘額為負數

if(account.money < drawingMoney)
			return;
146.synchronized同步方法與塊

對以上銀行取款改進

package zy.thread;

public class safeAccount {
	public static void main(String[] args) {
		synAccount a = new synAccount(100, "原麥山丘");
		syndrawing boy = new syndrawing(a, 60, "boyDrawing");
		syndrawing girl = new syndrawing(a, 80, "girlDrawing");
		boy.start();
		girl.start();
	}
}

//賬戶
class synAccount{
	int money;
	String name;
	public synAccount(int money, String name) {
		super();
		this.money = money;
		this.name = name;
	}
}

//取款的業務類
class syndrawing extends Thread{
	synAccount account;
	int drawingMoney;
	int packetTotal;
	
	public syndrawing(synAccount account, int drawingMoney, String name) {
		super(name);
		this.account = account;
		this.drawingMoney = drawingMoney;
	}
	
	public void run() {
		get();
	}
	
	/*
	 * 給get方法用synchronized,實際是給this加鎖
	 * 是不能達到執行緒安全的,鎖定的應該是操作的account
	 * 使用同步塊就可完成
	 */
	public void get() {
		/*同步塊:
		 * synchronized(account) {...}
		 * account稱之為同步監視器
		 * 同步監視器只能被一個執行緒鎖定
		 * 只有鎖定同步監視器的執行緒才可以執行,不然就處於同步阻塞狀態
		 */
		if(account.money < drawingMoney)
			return;
		synchronized(account) {
			if(account.money < drawingMoney)
				return;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			account.money -= drawingMoney;
			packetTotal += drawingMoney;
		}
		System.out.println(this.getName() + "賬戶餘額" + account.money);
		System.out.println(this.getName() + "口袋餘額" + packetTotal);
	}
}

同步塊語法:

synchronized(物件obj) {...}

obj稱之為同步監視器

  1. 同步監視器只能被一個執行緒鎖定

  2. 只有鎖定同步監視器的執行緒才可以執行,不然就處於同步阻塞狀態

在程式同步塊之前要再加上條件判斷,這樣可以提高效率,不用阻塞等待鎖定同步監視器才判斷

if(account.money < drawingMoney)
			return;
synchronized(account) {...}

總結:synchronized的成員方法,鎖定的實際時this物件 3
同步塊鎖的實際是成員方法所能訪問的某一個資料成員

操作容器

多執行緒在操作容器時,如果對容器不加鎖就會導致覆蓋操作,執行緒不安全

package zy.thread;

import java.util.List;
import java.util.ArrayList;

public class unsafeList {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		for(int i=0; i<10000; ++i) {
			new Thread(()->{
				list.add(Thread.currentThread().getName());
			}).start();
		}
		
		System.out.println("list元素個數:" + list.size());
	}
}

對操作的容器進行同步監視,達到執行緒安全

package zy.thread;

import java.util.List;
import java.util.ArrayList;

public class safeList {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		for(int i=0; i<10000; ++i) {
			new Thread(()->{
				synchronized(list) {
		        	list.add(Thread.currentThread().getName());
				}
			}).start();
		}
		
		System.out.println("list元素個數:" + list.size());
	}
}

  1. 併發時保證資料的正確性和高效能 ↩︎

  2. 同步 ↩︎

  3. 對this資料成員的訪問都需要加鎖 ↩︎

相關文章