1. 程式人生 > >Java多執行緒技術篇--執行緒的互斥與同步通訊

Java多執行緒技術篇--執行緒的互斥與同步通訊

一、使用銀行轉賬來解釋執行緒安全問題

同一個銀行賬戶,在同一時間發生了轉賬匯款(解釋:你刷卡消費了1000元的同時,你朋友給你轉賬了2000元)。這個時候是兩個執行緒操作同一個資料,但是執行緒執行是隨機的,沒有先後之分,這個問題就引發了執行緒安全問題。

二、使用synchronized程式碼塊及其原理

Synchronized的語義底層是通過一個monitor的物件來完成,其實wait/notify等方法也依賴於monitor物件,

這就是為什麼只有在同步的塊或者方法中才能呼叫wait/notify等方法,否則會丟擲java.lang.IllegalMonitorStateException的異常的原因。

三、使用synchronized方法

package com.sinwao.thread01;

public class ThreadSynchronized {
	
	public static void main(String[] args) {
		new ThreadSynchronized().init();
	}
	
	private void init() {
		Outputer outputer = new Outputer();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				while (true) {
					try {
						Thread.sleep(1000);
					} catch (Exception e) {
						// TODO: handle exception
						e.printStackTrace();
					}
					outputer.output3("Mcgrady");
				}
			}
		}).start();
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				while (true) {
					try {
						Thread.sleep(1000);
					} catch (Exception e) {
						// TODO: handle exception
						e.printStackTrace();
					}
					outputer.output3("Byrant");
				}
			}
		}).start();
	}
	
	static class Outputer{
		public void output(String name) {
			int len = name.length();
			synchronized (Outputer.class) {
				for (int i = 0; i < len; i++) {
					System.out.print(name.charAt(i));
				}
				System.out.println();
			}
			
		}
		
		public synchronized void output2(String name) {
			int len = name.length();
				for (int i = 0; i < len; i++) {
					System.out.print(name.charAt(i));
				}
				System.out.println();
		}
		
		public synchronized static void output3(String name) {
			int len = name.length();
				for (int i = 0; i < len; i++) {
					System.out.print(name.charAt(i));
				}
				System.out.println();
		}
	}

}


四、分析靜態方法所使用的同步監視器物件是什麼

因為output3是一個靜態方法,靜態方法要同步,必須要用到鎖物件,靜態方法執行的時候

不用建立類的例項物件,但是靜態方法的位元組碼物件已經在記憶體裡面了,所以在output中傳入Output.class,就可以實現同步。

重點:靜態方法只與位元組碼物件關聯  

五、面試題解析

子執行緒迴圈10次,接著主執行緒執行10 0次,再接著子執行緒迴圈10次,再接著主執行緒迴圈100,如此往復50次。

package com.sinwao.thread01;

public class ThreadCommunication {
	
	public static void main(String[] args) {
		Business business = new Business();
		new Thread(new Runnable() {
			@Override
			public void run() { 
				for (int i = 0; i < 50; i++) {
					/*synchronized (ThreadCommunication.class) {
						for (int j = 0; j < 10; j++) {
							System.out.println("子執行緒第" + j + "次執行,迴圈次數  " + i);
						}
					}*/
					/**
					 * 將這部分程式碼封裝起來,可以很高的解決了synchronized鎖的問題
					 * 講要用到共同資料(包括同步鎖)或者共同演算法的若干個方法應該歸在同一個類身上
					 * 這種設計正好體現了高類聚和程式的健壯性
					 */
					business.sub(i);
					
				}
			}
		}).start();
		/**
		 * 加了synchronized之後,子執行緒跟主執行緒互斥
		 */
		for (int i = 0; i < 50; i++) {
			/*synchronized (ThreadCommunication.class) {
				for (int j = 0; j < 10; j++) {
					System.out.println("主執行緒第" + j + "次執行,迴圈次數  " + i);
				}
			}*/
			business.main(i);
		}
		
	}
	
	

}

package com.sinwao.thread01;

public class Business {
	private boolean isSubThread = true;
	public synchronized void sub(int i) {
//		if (!isSubThread) {
		while(!isSubThread) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		for (int j = 0; j < 10; j++) {
			System.out.println("子執行緒第" + j + "次執行,迴圈次數  " + i);
		}
		isSubThread = !isSubThread;
		this.notify();
	}
	
	public synchronized void main(int i) {
//		if (isSubThread) {
		while(isSubThread) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		for (int j = 0; j < 100; j++) {
			System.out.println("主執行緒第" + j + "次執行,迴圈次數  " + i);
		}
		isSubThread = !isSubThread;
		this.notify();
	}
	
	/**
	 * 使用if和while的區別
	 * 檢視API的時候發現wait在使用if的時候會出現假喚醒情況,所以官方推薦使用while
	 */
	
}