線程同步synchronized和volatile

上篇經過一個簡單的例子說明了線程安全與不安全,在例子中不安全的狀況下輸出的結果剛好是逐個遞增的,爲何會產生這樣的結果呢,由於創建的Count對象是線程共享的,一個線程改變了其成員變量num值,下一個線程正巧讀到了修改後的num,因此會遞增輸出。java

要說明線程同步問題首先要說明Java線程的兩個特性,可見性和有序性。多個線程之間是不能直接傳遞數據交互的,它們之間的交互只能經過共享變量來實現。拿上篇博文中的例子來講明,在多個線程之間共享了Count類的一個對象,這個對象是被建立在主內存(堆內存)中,每一個線程都有本身的工做內存(線程棧),工做內存存儲了主內存Count對象的一個副本,當線程操做Count對象時,首先從主內存複製Count對象到工做內存中,而後執行代碼count.count(),改變了num值,最後用工做內存Count刷新主內存Count。當一個對象在多個內存中都存在副本時,若是一個內存修改了共享變量,其它線程也應該可以看到被修改後的值,此爲可見性。由上述可知,一個運算賦值操做並非一個原子性操做,多個線程執行時,CPU對線程的調度是隨機的,咱們不知道當前程序被執行到哪步就切換到了下一個線程,一個最經典的例子就是銀行匯款問題,一個銀行帳戶存款100,這時一我的從該帳戶取10元,同時另外一我的向該帳戶匯10元,那麼餘額應該仍是100。那麼此時可能發生這種狀況,A線程負責取款,B線程負責匯款,A從出內存讀到100B從主內存讀到100A執行減10操做,並將數據刷新到主內存,這時主內存數據100-10=90,而B內存執行加10操做,並將數據刷新到主內存,最後主內存數據100+10=110,顯然這是一個嚴重的問題,咱們要保證A線程和B線程有序執行,先取款後匯款或者先匯款後取款,此爲有序性
安全

看代碼,TranditionalThead.java建立兩個線程去訪問同一個方法多線程

package org.thread;

public class TranditionalThead {

	
	public static void main(String[] args) {
		final Outputer outputer = new Outputer();
		Runnable runnable1 = new Runnable(){
			public void run(){
				outputer.input("HeinrichChen");
			};
			
		};
		
		Runnable runnable2 = new Runnable(){
			
			public void run(){
				outputer.input("Heinrich.Shen");
			}
		};
		
		
		new Thread(runnable1).start();
		new Thread(runnable2).start();
	}
	
	 
}
package org.thread;

public class Outputer {
	
	public void input(String name){
		
			for(int i =0;i<name.length();i++){
				System.out.print(name.charAt(i));
			}
		
	}

}

運行的結果:併發

 

  HHeinrich.eSheninrichChen
  我想要的結果是
  HeinrichChenHeinrich.Shen


  這個時候他們會出現咱們不想要出現的事情   這就是線程同步問題,咱們但願output方法被一個線程完整的執行完以後在切換到下一個線程,   Java中使用synchronized保證一段代碼在多線程執行時是互斥的,有兩種用法:ide

第一種this

synchronized (this) {
			for(int i =0;i<name.length();i++){
				System.out.print(name.charAt(i));
			}
		}

鎖住咱們須要操做的代碼塊spa

使用synchronized將須要互斥的代碼包含起來,並上一把鎖。
線程

運行的結果是code

  HeinrichChenHeinrich.Shen

這就是我想要的結果對象

第二種

synchronized加在須要互斥的方法上。

也便是

HeinrichChenHeinrich.Shen

這種方式就至關於用this鎖住整個方法內的代碼塊,若是用synchronized加在靜態方法上,就至關於用××××.class鎖住整個方法內的代碼塊。使用synchronized在某些狀況下會形成死鎖,死鎖問題之後會說明。



每一個鎖對象都有兩個隊列,一個是就緒隊列,一個是阻塞隊列,就緒隊列存儲了將要得到鎖的線程,阻塞隊列存儲了被阻塞的線程,當一個線程被喚醒(notify)後,纔會進入到就緒隊列,等待CPU的調度,反之,當一個線程被wait後,就會進入阻塞隊列,等待下一次被喚醒,這個涉及到線程間的通訊,下一篇博文會說明。看咱們的例子,當第一個線程執行輸出方法時,得到同步鎖,執行輸出方法,剛好此時第二個線程也要執行輸出方法,但發現同步鎖沒有被釋放,第二個線程就會進入就緒隊列,等待鎖被釋放。一個線程執行互斥代碼過程以下:

1. 得到同步鎖;

2. 清空工做內存;

3. 從主內存拷貝對象副本到工做內存;

4. 執行代碼(計算或者輸出等)

5. 刷新主內存數據;

6. 釋放同步鎖。

因此,synchronized既保證了多線程的併發有序性,又保證了多線程的內存可見性。

volatile是第二種Java多線程同步的手段,根據JLS的說法,一個變量能夠被volatile修飾,

在這種狀況下內存模型確保全部線程能夠看到一致的變量值,來看一段代碼:

package org.thread;

public class CountNum {

	public static int i = 0, j = 0;

	public static void count() {

		for (int m = 0; m < 10000; m++) {

			j++;

			i++;
		}

	}

	public static void show() {
		if (i < j) {
			System.out.println("i==>" + i + "==="
					+ Thread.currentThread().getName());
			System.out.println("j==>" + j + "==="
					+ Thread.currentThread().getName());
		}
	}
}

一些線程執行one方法,另外一些線程執行count方法,show方法有可能打印出j比i大的值,按照以前分析的線程執行過程分析一下:

1. 將變量i從主內存拷貝到工做內存;

2. 改變i的值;

3. 刷新主內存數據;

4. 將變量j從主內存拷貝到工做內存;

5. 改變j的值;

6. 刷新主內存數據;

這個時候執行show方法的線程先讀取了主存i原來的值又讀取了j改變後的值,這就致使了程序的輸出不是咱們預期的結果,那麼能夠在共享變量以前加上volatile

package org.thread;

public class CountNum {

	public volatile static int i = 0, j = 0;

	public static void count() {

		for (int m = 0; m < 10000; m++) {

			j++;

			i++;
		}

	}

	public static void show() {
		if (i < j) {
			System.out.println("i==>" + i + "==="
					+ Thread.currentThread().getName());
			System.out.println("j==>" + j + "==="
					+ Thread.currentThread().getName());
		}
	}
}
package org.thread;

public class CountTest {
	
	public static void main(String[] args) {
		
		
		Runnable runnable1 = new Runnable() {
			
			@Override
			public void run() {
				
				new CountNum().count();
			}
		};
		
		Runnable runnable2 = new Runnable() {
			
			@Override
			public void run() {
				new CountNum().show();
			}
		};
		
		
		for(int i=0;i<1000;i++){
			new Thread(runnable1).start();
			new Thread(runnable2).start();
		}
		
	}
	

}
i==>4938===Thread-3
j==>7160===Thread-3
i==>7951===Thread-7
j==>8328===Thread-7
i==>8814===Thread-11
j==>9169===Thread-11
i==>18925===Thread-15

加上volatile能夠將共享變量ij的改變直接響應到主內存中,這樣保證了ij的值能夠保持一致,然而咱們不能保證執行two方法的線程是在ij執行到什麼程度獲取到的,因此volatile能夠保證內存可見性,不能保證併發有序性。

相關文章
相關標籤/搜索