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方法則會全部喚醒,儘管只調用一次
//www.dtmao.cc/news_show_676906.shtml