Java併發基礎03:傳統線程的互斥技術—synchronized

歡迎關注個人微信公衆號:程序員私房菜(id:eson_15)java

在多個線程同時操做相同資源的時候,就會遇到併發的問題,如銀行轉帳啊、售票系統啊等。爲了不這些問題的出現,咱們可使用 synchronized 關鍵字來解決,下面針對 synchronized 常見的用法作一個總結。首先寫一個存在併發問題的程序,以下:程序員

public class TraditionalThreadSynchronized {

	public static void main(String[] args) {
		//在靜態方法中不能new內部類的實例對象
		//private Outputer outputer = new Outputer();
		new TraditionalThreadSynchronized().init();
	}
	
	private void init() {
		final Outputer outputer = new Outputer();
		//線程1打印:duoxiancheng
		new Thread(new Runnable() {			
			@Override
			public void run() {
				while(true) {
					try {
						Thread.sleep(5);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					outputer.output1("duoxiancheng");
				}
				
			}
		}).start();;
		
		//線程2打印:eson15
		new Thread(new Runnable() {			
			@Override
			public void run() {
				while(true) {
					try {
						Thread.sleep(5);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					outputer.output1("eson15");
				}
				
			}
		}).start();;
	}
	
	static class Outputer {
		//自定義一個字符串打印方法,一個個字符的打印
		public void output1(String name) {
			int len = name.length();
			for(int i = 0; i < len; i++) {
				System.out.print(name.charAt(i));
			}
			System.out.println("");		
		}		
	}
}
複製代碼

在內部類 Outputer 中定義了一個打印字符串的方法,一個字符一個字符的打印,這樣比較容易直觀的看出併發問題,由於字符順序打亂了就說明出現問題了。而後在init方法中開啓兩個線程,一個線程打印「duoxiancheng」,另外一個線程打印「eson15」。看一下運行結果:安全

eson15duoxianche ng eson15 duoxiancheng duoxiancheng eson15 esduoxiancheng on15 duoxiancheng微信

已經出現問題了,爲了解決這個問題,可使用 synchronized 同步代碼塊來對公共部分進行同步操做,可是須要給它一把鎖,這把鎖是一個對象,能夠是任意一個對象,可是前提是,兩個線程使用的必須是同一個對象鎖才能夠,這很好理解。那麼下面在 output1() 方法中加入 synchronized 代碼塊:併發

static class Outputer {
	private String token = ""; //定義一個鎖
	public void output1(String name) {
		synchronized(token) //任何一個對象均可以做爲參數,可是該對象對於兩個線程來講是同一個才行
		//若是用name就不行了,由於不一樣的線程進來name是不同的,不是同一個name
		{
			int len = name.length();
			for(int i = 0; i < len; i++) {
				System.out.print(name.charAt(i));
			}
			System.out.println("");		
		}
	}
}	
複製代碼

通過上面的改造,線程安全問題就基本解決了,可是還能夠再往下引伸,若是在方法上加 synchronized 關鍵字的話,那麼這個同步鎖是什麼呢?咱們在 Outputer 類中再寫一個output2()方法:ide

static class Outputer {
	private String token = ""; //定義一個鎖
	public void output1(String name) {
		synchronized(token) //任何一個對象均可以做爲參數,可是該對象對於兩個線程來講是同一個才行
		{
			int len = name.length();
			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("");		
	}	
}
複製代碼

方法內部實現邏輯如出一轍,惟一不一樣的就是 synchronized 加在了方法上,那麼咱們讓 init() 方法中的兩個線程中,一個調用 output1(String name) 方法,另外一個調用 output2(String name) 方法,從結果中能看出,線程安全問題又出現了。產生問題的緣由不難發現:如今兩個方法都加了 synchronized,可是兩個線程在調用兩個不一樣的方法仍是出現了問題,也就是說,仍是各玩各的……那麼問題就出在這個鎖上,說明二者並無使用同一把鎖!函數

若是咱們把 output1() 方法中 synchronized 中的 token 改爲 this,再運行就沒問題了,這說明一點:synchronized關鍵字修飾方法的時候,同步鎖是 this,即等效於代碼塊synchronized(this) {...}this

再繼續往下引伸,如今在 Outputer 類中再寫一個靜態方法output3(String name),而且也讓 synchronized 去修飾這個靜態方法。spa

static class Outputer {
	private String token = ""; //定義一個鎖
	public void output1(String name) {
		synchronized(token) //任何一個對象均可以做爲參數,可是該對象對於兩個線程來講是同一個才行
		{
			int len = name.length();
			for(int i = 0; i < len; i++) {
				System.out.print(name.charAt(i));
			}
			System.out.println("");		
		}
	}	
	
	public static synchronized void output3(String name) {
		
		int len = name.length();
		for(int i = 0; i < len; i++) {
			System.out.print(name.charAt(i));
		}
		System.out.println("");		
		}	
	}
}
複製代碼

而後在 init() 方法中一個線程調用 output1() 方法,另外一個線程調用 output3() 方法。毫無疑問,確定又會出現線程安全問題。可是如何解決呢?由於 static 方法在類加載的時候就加載了,因此這個鎖應該是類的字節碼對象。那麼將 token 改成 Outputer.class 就解決問題了,這說明一點:synchronized關鍵字修飾static方法的時候,同步鎖是該類的字節碼對象,即等效於代碼塊 synchronized(classname.class) {...}。    最後再總結一下:線程

  • 同步代碼塊的鎖是任意對象。只要不一樣的線程都執行同一個同步代碼塊的時候,這個鎖隨便設。
  • 同步函數的鎖是固定的this。當須要和同步函數中的邏輯實行同步的時候,代碼塊中的鎖必須爲this。
  • 靜態同步函數的鎖是該函數所屬類的字節碼文件對象。該對象能夠用 this.getClass() 方法獲取,也可使用 當前類名.class 表示。

OK,synchronized 就總結這麼多~若有錯誤之處,歡迎指正~咱們一塊兒進步!

也歡迎你們關注個人微信公衆號:程序員私房菜。我會持續輸出更多文章。

公衆號
相關文章
相關標籤/搜索