1. 程式人生 > >Java複習-併發程式設計中的三個問題:原子性、可見性和有序性

Java複習-併發程式設計中的三個問題:原子性、可見性和有序性

在併發程式設計中,我們通常會遇到以下三個問題:原子性問題,可見性問題,有序性問題。

1、原子性:

原子性:即一個操作或者多個操作 要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行。

2、可見性

可見性:當多個執行緒訪問同一個變數時,一個執行緒修改了這個變數的值,其他執行緒能夠立即看得到修改的值。

3、有序性

有序性:即程式執行的順序按照程式碼的先後順序執行。

當程式在執行過程中,會將運算需要的資料從主存中複製一份到CPU的快取記憶體當中,那麼CPU進行計算時就可以直接從它的快取記憶體讀取資料和向其中寫入資料,當運算結束之後,再將快取記憶體中的資料重新整理到主存當中。這三個問題主要就是在這個從主存中讀入資料到快取記憶體及從快取記憶體寫出資料到主存的過程中,資料操作可能遇到的問題。


先列一份程式碼:

package BasicConcept;

import java.time.Year;

public class BasicConcurrent {
	
	int x = 0;
	int y = 0;
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		BasicConcurrent basicConcurrent = new BasicConcurrent();
		Thread t1 = new Thread(basicConcurrent.new TrdOne());
		Thread t2 = new Thread(basicConcurrent.new TrdTwo());
		t1.start();
		t2.start();
		System.out.println("x + y = " + (basicConcurrent.x+basicConcurrent.y));
	}
	
	class TrdOne implements Runnable {

		@Override
		public void  run() {
			// TODO Auto-generated method stub
			for (int i = 0; i < 20; i++) {
				try {
					x ++;
					Thread.sleep(100);
					y =x;
					System.out.println("x = " + x + " " + "y = " + y + " " +Thread.currentThread().getName());
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		
	}
	
	class TrdTwo implements Runnable {

		@Override
		public void run() {
			// TODO Auto-generated method stub
			for (int i = 0; i < 20; i++) {
				try {
					Thread.sleep(20);
					System.out.println("x = " + x + " "+ "y = " + y + " " +Thread.currentThread().getName());
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		
	}
	
	
}

下面是上面的執行結果:



我們分析一下,我們知道新的執行緒啟動之後,在多核處理器上是各自執行的。上面的執行結果我們可以看到,主執行緒main中用來輸出當前的x與y的值,而x與y的值是通過Thread0與Thread1去改變的。每次Thread0從主存中讀取當前的x與y值,對x實現x++,之後輸出x與y的值;而Thread1也是從主存中讀取當前的x與y的值,之後輸出x與y的值。

從結果中,我們可以看到在原子性方面如果想實現:一邊x++後暫時之後再實現y=x,另一遍實現輸出x與y,利用這兩個執行緒可能出現x與y值並不等,每次極有可能出現x已經++了,但是此時掛起了Thread0,而Thread1並不能馬上讀出準確值,反而讀的是之前主存裡存放的x與y值,這個小情況就體現出原子性常見的問題。

而在可見性上,其實和之前的原子性有類似的原因,正是因為Thread0和Thread1的可能暫時掛起當前執行緒,極有可能產生Thread1每次讀到的x與y值都是老值,都不是Thread0處理之後的值,這點可以視為其可見性方面的問題。

在有序性上,程式可能由於x和y沒有資料依賴性,因此可能會被重排序。假如發生了重排序,在Thread0執行過程中先執行x++可能會暫時放棄執行y=x,而此時Thread1會執行輸出,並不能保證到最後y與x一樣大。

在此順便提下volatile關鍵字,synchronized關鍵字是防止多個執行緒同時執行一段程式碼,那麼就會很影響程式執行效率,而volatile關鍵字在某些情況下效能要優於synchronized,但是要注意volatile關鍵字是無法替代synchronized關鍵字的,因為volatile關鍵字無法保證操作的原子性。通常來說,使用volatile必須具備以下2個條件:

1)對變數的寫操作不依賴於當前值
2)該變數沒有包含在具有其他變數的不變式中 實際上,這些條件表明,可以被寫入 volatile 變數的這些有效值獨立於任何程式的狀態,包括變數的當前狀態。
就我的理解volatile關鍵字是保證了物件的可見性,但是無法保證其原子性,同時還能部分保證其有序性。