1. 程式人生 > >Java中詳述執行緒間協作

Java中詳述執行緒間協作

執行緒協作

首先引入一段程式碼:

package 執行緒間資料共享;

import java.util.Date;

public class Watch {

	private static String time;
	
	static class Display extends Thread{
		Time timeThread;
		
		Display(Time time) {
			this.timeThread=time;
		}
		
		@Override
		public void run() {
			if(time==null){
			
			}
			System.out.println(time);
		}
	}
	
	static class Time extends Thread{
		
		@Override
		public void run() {
			time = new Date().toString();
		}
	}
	
	public static void main(String[] args) {
		Time time = new Time();
		time.start();
		new Display(time).start();
	}
}

結果顯示為:

之後我們在if(time==null){ }語句程式碼塊中新增,使其能夠正常輸出(輸出結果不為null)
程式碼展示為:

package 執行緒間資料共享;

import java.util.Date;

public class Watch {

	private static String time;
	
	static class Display extends Thread{
		Time timeThread;
		Object object;
		
		Display(Time time) {
			this.timeThread=time;
		}
		
		@Override
		public void run() {
			if(time==null) {
				try {
					sleep(1500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println(time);
		}
	}
	
	static class Time extends Thread{
		
		@Override
		public void run() {
			time = new Date().toString();
		}
	}
	
	public static void main(String[] args) {
		final Object OBJECT = new Object();
		Time time = new Time();
		time.start();
		new Display(time).start();
	}
}

結果顯示為:

我們通過sleep()方法實現了正常的輸出,但是在實際操作過程中,我們無法判斷sleep()中所需要限制的時間,因此我們需要通過另一種方法來實現:即使用join()方法,程式碼展示為:

package 執行緒間資料共享;

import java.util.Date;

public class Watch {

	private static String time;
	
	static class Display extends Thread{
		Time timeThread;
		Object object;
		
		Display(Time time) {
			this.timeThread=time;
		}
		
		@Override
		public void run() {
			if(time==null) {
				try {
					timeThread.join();//執行緒A執行“已死”執行緒B所呼叫的jion方法,則執行緒A不會阻塞。
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println(time);
		}
	}
	
	static class Time extends Thread{
	
		@Override
		public void run() {
			time = new Date().toString();
		}
	}
	
	public static void main(String[] args) {
		final Object OBJECT = new Object();
		Time time = new Time();
		time.start();
		new Display(time).start();
	}
}

結果顯示為:

如果不使用join()方法,我們還可以選擇使用synchronized ()關鍵字的形式實現,程式碼展示為:

package 執行緒間資料共享;

import java.util.Date;

public class Watch {

	private static String time;
	
	static class Display extends Thread{
		Time timeThread;
		Object object;
		
		Display(Time time,Object object) {
			this.timeThread=time;
			this.object=object;
		}
		
		@Override
		public void run() {
			if(time==null) {
				synchronized (object) {
					try {//如果沒有synchronized關鍵字,則會出現報錯
						object.wait();//執行緒執行該方法,則該執行緒阻塞,知道呼叫notify方法
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
			System.out.println(time);
		}
	}
	
	static class Time extends Thread{
		Object object;
		Time(Object object){
			this.object=object;
		}
		
		@Override
		public void run() {
			time = new Date().toString();
			synchronized (object) {
				object.notify();
			}
		}
	}
	
	public static void main(String[] args) {
		final Object OBJECT = new Object();
		Time time = new Time(OBJECT);
		time.start();
		new Display(time,OBJECT).start();
	}
}

結果顯示為:

Object類中解決執行緒間協作的方法

synchronized關鍵字只是起到了多個執行緒“序列”執行臨界區中程式碼的作用,但是哪個執行緒先執行,哪個執行緒後執行依無法確定。Object類中的wait()、notify()和notifyAll()三個方法解決了執行緒間的協作問題,通過這三個方法的“合理”使用可以確定多執行緒中執行緒的先後執行順序:
1、wait():物件鎖呼叫了wait()方法會使當前持有該物件鎖的執行緒處於執行緒等待狀態同時該執行緒釋放對物件鎖的控制權,直到在其他執行緒中該物件鎖呼叫notify()方法或notifyAll()方法時等待此物件鎖的執行緒才會被喚醒。
2、notify():物件鎖呼叫notify()方法就會喚醒在此物件鎖上等待的單個執行緒。
3、notifyAll():物件鎖呼叫notifyAll()方法就會喚醒在此物件鎖上等待的所有線、程;呼叫notifyAll()方法並不會立即啟用某個等待執行緒,它只能撤銷等待執行緒的中斷狀態,這樣它們就能夠在當前執行緒退出同步方法或同步程式碼塊法後與其它執行緒展開競爭,以爭取獲得資源物件來執行。

誰呼叫了wait方法,誰就必須呼叫notify或notifyAll方法,並且“誰”是物件鎖。

使用Object類中的wait()、notify()和notifyAll()三個方法需要注意以下幾點:
1、wait()方法需要和notify()或notifyAll()方法中的一個配對使用,且wait方法與notify()或notifyAll()方法配對使用時不能在同一個執行緒中(參見程式碼1)。
2、wait()方法、notify()方法和notifyAll()方法必須在同步方法或者同步程式碼塊中使用,否則出現IllegalMonitorStateException 異常。
3、呼叫wait()方法、notify()方法和notifyAll()方法的物件必須和同步鎖物件是一個物件。

sleep()方法和wait()方法區別

1、sleep()方法被呼叫後當前執行緒進入阻塞狀態,但是當前執行緒仍然持有物件鎖,在當前執行緒sleep期間,其它執行緒無法執行sleep方法所在臨界區中的程式碼。

package 執行緒間資料共享;

import java.util.Date;

public class sleep和wait區別 {

		public static void main(String[] args) {
			Object lockObj = new Object();
			new PrintThread("1號印表機"+new Date(),lockObj).start();
			new PrintThread("2號印表機"+new Date(),lockObj).start();
		}
	}

	class PrintThread extends Thread {
		
		private Object lockObj;

		public PrintThread(String threadName, Object lockObj) {
			super(threadName);
			this.lockObj = lockObj;
		}

		@Override
		public void run() {
			synchronized (lockObj) {
				System.out.println(getName());
				try {
					sleep(3000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
}

結果顯示為:

由此我們可以看出:當某臺印表機執行臨界區中的程式碼,輸出執行緒名後由於呼叫了sleep方法,從而使得該印表機執行緒阻塞3秒,在這3秒期間因該印表機執行緒仍然持有物件鎖,從而導致另一臺印表機執行緒只能在3秒後才能執行臨界區中的程式碼。(但是所執行的時間是相同的)
2、物件鎖呼叫了wait()方法會使當前持有該物件鎖的執行緒處於執行緒等待狀態同時該執行緒釋放對物件鎖的控制權,在當前執行緒處於執行緒等待期間,其它執行緒可以執行wait方法所在臨界區中的程式碼。

package 執行緒間資料共享;

import java.util.Date;

public class sleep和wait區別 {

		public static void main(String[] args) {
			Object lockObj = new Object();
			new PrintThread("1號印表機"+new Date(),lockObj).start();
			new PrintThread("2號印表機"+new Date(),lockObj).start();
		}
	}

	class PrintThread extends Thread {
		
		private Object lockObj;

		public PrintThread(String threadName, Object lockObj) {
			super(threadName);
			this.lockObj = lockObj;
		}

		@Override
		public void run() {
			synchronized (lockObj) {
				System.out.println(getName());
				try {
					lockObj.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

結果顯示為:

wait()方法

程式碼一:

package 執行緒間資料共享;
class CounterThread extends Thread {

	private Object lockObj;

	public CounterThread(String threadName, Object lockObj) {
		super(threadName);
		this.lockObj = lockObj;
	}

	@Override
	public void run() {
		int i = 1;
		while (true) {
			synchronized (lockObj) {
				System.out.println(getName() + ":" + i);
				try {
					lockObj.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				i++;
			}
		}
	}
}

public class Test {

	public static void main(String[] args) {
		Object lockObj = new Object();
		new CounterThread("1號計數器",lockObj).start();
		new CounterThread("2號計數器",lockObj).start();
	}
}


結果顯示為:

儘管while(){xxx}迴圈是死迴圈,但由於物件鎖lockObj呼叫了wait()方法,使得分別持有該lockObj物件鎖的“1號計數器”執行緒和“2號計數器”執行緒處於執行緒等待狀態,所以迴圈並沒有繼續下去
程式碼二:

package 執行緒間資料共享;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

public class sleep和wait區別 {
	
		public static void main(String[] args) {
			TimeThread timeThread = new TimeThread ();
			timeThread.start();

			synchronized (timeThread) {
				try {
					timeThread.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("main方法");//由於主執行緒進入了阻塞狀態,所以該行程式碼不執行
		}
	}

	class TimeThread extends Thread{

		@Override
		public void run() {
			DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
			while (true) {
				String currentTime = dateFormat.format(new Date());
				System.out.println(currentTime);
				try {
					sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

結果顯示為:

為什麼時間執行緒沒有進入阻塞狀態呢?
timeThread變數所代表的物件充當物件鎖,由於該程式碼在main方法中,也就是說將來由主執行緒執行臨界區中的程式碼,也就是說主執行緒是持有物件鎖的執行緒,主執行緒執行完“timeThread.wait()”程式碼後進入阻塞狀態,是主執行緒進入阻塞狀態而非時間執行緒,這也是main方法中“System.out.println(“main方法”);”程式碼不執行的原因。

notify()方法和notifyAll()方法

notify()方法

import java.text.SimpleDateFormat;
import java.util.Date;

public class ElectronicWatch {
	
	String currentTime;
	DisplayThread displayThread = new DisplayThread();
	private static final Object lockObject = new Object();
	
	public static void main(String[] args) {
		new ElectronicWatch().displayThread.start();
	}

	/**
	 * 該執行緒負責顯示時間
	 */
	class DisplayThread extends Thread{
		
		@Override
		public void run() {
			synchronized (lockObject) {
				new TimeThread().start();
				try {
					lockObject.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(currentTime);
			}
		}
	}

	/**
	 * 該執行緒負責獲取時間
	 */
	class TimeThread extends Thread{
		
		@Override
		public void run() {
			synchronized (lockObject) {
				try {
					sleep(3000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				String pattern = "yyyy-MM-dd HH:mm:ss";
				SimpleDateFormat sdf = new SimpleDateFormat(pattern);
				currentTime = sdf.format(new Date());
				lockObject.notify();
			}
		}
	}
}

結果顯示為:

notifyAll()方法

package 執行緒間資料共享;
class CounterThread extends Thread {

	private Object lockObj;

	public CounterThread(String threadName, Object lockObj) {
		super(threadName);
		this.lockObj = lockObj;
	}

	@Override
	public void run() {
		int i = 1;
		while (true) {
			synchronized (lockObj) {
				System.out.println(getName() + ":" + i);
				try {
					lockObj.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				i++;
			}
		}
	}
}

class NotifyAllThread extends Thread {

	private Object lockObj;

	public NotifyAllThread(Object lockObj) {
		this.lockObj = lockObj;
	}

	@Override
	public void run() {
		synchronized (lockObj) {
			System.out.println("notifyAll方法執行完畢");
			lockObj.notifyAll();
		}
	}
}

public class Test {

	public static void main(String[] args) {
		Object lockObj = new Object();
		new CounterThread("1號計數器", lockObj).start();
		new CounterThread("2號計數器", lockObj).start();
		
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		new NotifyAllThread(lockObj).start();
	}
}

結果顯示為:

如果呼叫的是notify方法,則只會喚醒在lockObj物件鎖上等待的兩個執行緒中的一個;
而呼叫notifyAll方法則會全部喚醒,儘管只調用一次
http://www.dtmao.cc/news_show_676906.shtml