1. 程式人生 > >併發程式設計(二)-訪問共享資源

併發程式設計(二)-訪問共享資源

當代碼中多個執行緒任務訪問同一共享資源時,就會引發衝突,目前解決多執行緒衝突問題都是採用序列化訪問共享資源的解決方案,即將共享資源放在某一程式碼塊中並加鎖,某一時刻只能有一個執行緒訪問該程式碼塊.
同步的規則:"如果你正在寫一個變數,它接下來可能被另一個執行緒讀取,或者你正在讀取一個已經被其他執行緒改寫的變數,那麼你需要使用同步,並且讀寫執行緒必須使用相同的監視器同步".
1)Synchronized同步方法

共享資源在記憶體中以物件的形式存在,訪問該物件的方法如果需要加鎖,則新增synchronized關鍵字構成同步方法.當在一個執行緒中呼叫某物件的同步方法時,在同一時間其他執行緒中不能再呼叫該物件的其他同步方法或同步塊;

public class SynchBrock {
	public static void main(String[] args) {
		Task t=new Task();
		for(int i=0;i<5;i++){
			new Thread(){
				public void run(){
					while(true){
						t.increment();
					}
				}
			}.start();
		}
		while(true){
			if(t.get()%2!=0){
				System.out.println(t.get());
				System.exit(0);
			}
		}
	}
}
class Task{
	public int i=0;
	public synchronized void increment(){
		i++;
		i++;
	}
	public synchronized int get(){
		return this.i;
	}
}
在上例中共享資源為t.i,訪問共享變數的共有兩個方法increment()和get(),根據同步規則,兩個方法都需要同步.同一時間只會有一個執行緒訪問其中的一個方法,因此不會出現奇數的情況.

2)Synchronized同步塊
使用Synchronized關鍵字建立同步塊(臨界區)時需要給定一個在其上進行同步的物件,最合理的是使用呼叫當前方法的物件即:synchronized(this),此時在其他執行緒中就不能訪問該物件的其他synchronized方法或臨界區;

public class SynchBrock {
	public static void main(String[] args) {
		Task t=new Task();
		for(int i=0;i<5;i++){
			new Thread(){
				public void run(){
					while(true){
						t.increment();
					}
				}
			}.start();
		}
		while(true){
			if(t.get()%2!=0){
				System.out.println(t.get());
				System.exit(0);
			}
		}
	}
}
class Task{
	public int i=0;
	public void increment(){
		synchronized(this){
			i++;
			i++;
		}
	}
	public synchronized int get(){
		return this.i;
	}
}
上例中將increment()方法中關鍵程式碼部分改成synchronized同步塊,由於此時同步塊同步的物件為this,因此get()同步方法也會在任務之間互斥,因此同一時間只會有一個執行緒訪問其中的一個方法;
假如synchronized同步塊在另外一個物件上同步,那麼會出現同一時間兩個執行緒分別訪問increment()和get()方法;
public class SynchBrock {
	public static void main(String[] args) {
		Task t=new Task();
		for(int i=0;i<5;i++){
			new Thread(){
				public void run(){
					while(true){
						t.increment();
					}
				}
			}.start();
		}
		while(true){
			if(t.get()%2!=0){
				System.out.println(t.get());
				System.exit(0);
			}
		}
	}
}
class Task{
	public int i=0;
	Object ob=new Object();
	public void increment(){
		synchronized(ob){
			i++;
			i++;
		}
	}
	public synchronized int get(){
		return this.i;
	}
}
3)Lock同步塊
Lock物件必須被顯示的建立,鎖定和釋放.使用時需要注意兩點:1)必須在finally中釋放鎖;2)return語句必須在try語句中,確保unlock不會過早發生.
public class SynchBrock {
	public static void main(String[] args) {
		Task t=new Task();
		for(int i=0;i<5;i++){
			new Thread(){
				public void run(){
					while(true){
						t.increment();
					}
				}
			}.start();
		}
		while(true){
			if(t.get()%2!=0){
				System.out.println(t.get());
				System.exit(0);
			}
		}
	}
}
class Task{
	public int i=0;
	private Lock lock=new ReentrantLock();
	public void increment(){
		lock.lock();
		try{
			i++;
			i++;
			return;
		}
		finally{
			lock.unlock();
		}	
	}
	public synchronized int get(){
		return this.i;
	}
}
多執行緒訪問共享資源則需要加鎖,加鎖必須明白要對誰加鎖,同步方法是對該方法的物件進行加鎖,同步塊必須指出要加鎖的物件,當訪問某加鎖物件的同步方法時,同一時刻其他執行緒中不能訪問該物件的其他同步方法或同步塊.