1. 程式人生 > >java執行緒對單個物件的共享的一些方式

java執行緒對單個物件的共享的一些方式

public class VisibilityTest {


	private static boolean flag;
	public static void main(String[] args) throws InterruptedException {
		
		new Thread(new ExcuteThread()).start();
		Thread.sleep(1000);
		flag=true;
		System.out.println(flag);
	}
	private static class ExcuteThread implements Runnable
	{
		@Override
		public void run() {
			while(!flag){}
		}
	}
}
	

最後的執行結果是:控制檯會輸出true,但是程式不會停止。 

上述圖片是JVM的記憶體模型(JVM的一些知識在隨後的部落格中會寫),是我從別人部落格中複製過來的,這裡說明一下。 jvm執行時有一個虛擬機器棧和 堆區、方法區,其中虛擬機器棧是執行緒私有的,而堆區和方法區是共享的。上述圖片的工作記憶體可以簡單的理解為虛擬機器棧,主記憶體理解為堆區、方法區。也就是所有執行緒共享的記憶體。再看上面的程式碼,其中
private static boolean flag;
這個flag是類的靜態成員變數,所以存在與方法區,是執行緒共享的,所以當有一個執行緒在執行 某個方法(如上述程式碼的run方法)使用這個變數時,這個執行緒就會通過一系列的操作,將主記憶體的flag複製到自己的工作記憶體。而當主執行緒MAIN()方法中修改了主記憶體flag,但是修改完之前,原來的執行緒已經將flag的值呼叫到了自己的工作記憶體,此時原來的執行緒就不會再去主記憶體中訪問該變數,直接就從工作記憶體中訪問該變數的快取。所以就造成了這個flag變數值還是原先的變數。這個變數就是不可見的。如果將變數寫成volidate型別,該變數就是可見的。更詳細的的請參考下面的連結,寫的挺全面http://www.th7.cn/Program/java/201312/166504.shtml

1.1 非原子的64位操作

在jvm記憶體模型中,從記憶體往工作記憶體中複製都是原子性的,比如int型的資料在記憶體中佔32位的空間,從記憶體往工作記憶體中複製時32個位元組要一起全部複製到工作記憶體中,而long和double型別的資料,在記憶體中佔用64位位元組,JVM允許將64位的讀寫操作分兩次32位的操作。所以當記憶體中有個變數long number=1;如果當一個執行緒要訪問這個變數時,而同時另一個執行緒對number變數修改為20,此時第一個執行緒可能只讀到number前32位,而後再讀的時候,可能是修改後的值的後32,所以得到的值可能既不是1,也不是20.

1.2 Volidate修飾變數

java提供了一個稍弱的同步機制,用volidate修飾變數,此時變數具備了可見性,當執行緒讀取該變數時,他會從記憶體中去讀取,不會再讀取工作記憶體中的變數副本。修改該變數時也會更新記憶體中該變數的值。 但是,volidate修飾的變數,是無法保證變數的同步性的。這裡就不寫程式碼了,簡單的解釋下。具體的知識的上面的地址中有寫到,同時推薦《深入理解JAVA虛擬機器》這本書,這本書中也有詳細介紹。因為volidate修飾的變數雖然可以保證變數的可見性,也就是每次讀取該變數的值的時候都會從主記憶體中去讀取。當A執行緒讀取該變數時,在A執行緒還未講該變數修改的值同步到主記憶體中的時候,執行緒B此時也要讀取該變數的值,所以就會造成該變數的不同步問題。 Volidate修飾的變數也可以解決指標重排序的問題(在上述連結和推薦的那本書有詳細描述)。 理解Volidate對多執行緒的理解是很有幫助的。

2.釋出和逸出

所謂釋出,簡單的解釋就是A執行緒建立了一個物件,而其他的執行緒可以看到這個物件,那麼該物件就被髮布了。
public class PublishEscape {

	private static PublishEscape pe = null;
	
	private PublishEscape(){
	}
	
	public static <span style="font-family: Arial, Helvetica, sans-serif;">PublishEscape getInstance(){</span>
		if(pe == null){
			pe = new PublishEscape();
		}
		return pe;
	}
}

上述程式碼為一個最簡單的單例模式,存在的問題顯而易見,缺少同步控制,當A執行緒呼叫getInstance方法是,發現pe==null,此時執行緒B同時也呼叫該方法,也會發現pe==null,此時就會建立兩個PublishEscape例項。本來單例模式只准建立一個物件例項。這個算是物件的逸出。還有一種是物件釋出出去後,他的狀態可以隨意發生改變,或者狀態不一致等。都算逸出(這個概念有點模糊,因為水平有限,我也沒辦法說的太細)
public class Escape {
	private String []state = new String[]{"A","B"};
	public String[] getSate(){
		return state;
	}
}
如上面的程式碼,當Escape物件釋出出去後,任何Escape物件的呼叫者都可以隨意更改state的內容,所以state就已經逸出了它的作用域

2.1 this指標逸出

public class ThisEscape {
	
	private int a = 0;

	public ThisEscape(){
		EventClass event = new EventClass();
		new Thread(event).start();
		a = 10;
	}

	class EventClass implements Runnable{
		
		@Override
		public void run() {
			System.out.println(a);
		}
		
	}
	public static void main(String args[]){
		 new ThisEscape();
	}
}
如上面的例子,因為內部類EventClass包含了對外部類ThisEscape的引用,當內部類的執行緒輸出a變數的時候,外部類的a可能還沒有進行a=10這一步操作,造成了狀態不一致。所以就造成了ThisEscape這個類的this逸出。以後要防止在建構函式中this逸出的情況。

3 執行緒封閉

執行緒封閉主要是將某個變數封裝在某個執行緒內,其他執行緒無法訪問到該變數,例如區域性變數,ThreadLocal維持的變數。主要介紹下ThreadLocal。

3.1 ThreadLocal類

ThreadLocal類主要是執行緒將某個記憶體共享的類或變數,在堆記憶體中建立一份只有當前執行緒可以訪問的物件。這個物件是其他執行緒所看不到的。下面先看一下ThreadLocal 類
public class ThreadLocal<T> {

	public void set(T value) {
	        Thread t = Thread.currentThread(); //得到當前執行緒
			/**
			得到當前執行緒下對應的ThreadLocal物件。Thread類中有一個ThreadLocal.ThreadLocalMap變數 threadLocals。
			*/

		   ThreadLocalMap map = getMap(t);    //如果當前執行緒第一次執行這個方法,map肯定等於null,所以程式會走到createMap這個方法。
	        if (map != null)
	            map.set(this, value);
	        else
	            createMap(t, value); 
	 }
	 
	 void createMap(Thread t, T firstValue) {
		 
		/**
			這一步是建立一個ThreadLocalMap物件,然後放到當前執行緒的threadLocals這個變數中。
			而這個ThreadLocalMap 是ThreadLocal類的一個靜態內部類,見下面的程式碼
		*/
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
	
	
	  static class ThreadLocalMap {
		private Entry[] table;
      
		ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
			/**
				Entry 類又是ThreadLocalMap類的一個靜態內部類。看下面的Entry類,
				而真正我們起初呼叫的set(T value)這個方法的value值是存在了Entry類中的value變數中。
			*/
            table = new Entry[INITIAL_CAPACITY]; 
			/**
				這個i很重要,他通過當前的這個ThredLocal物件中的threadLocalHashCode來的得到的I值。
				因為現在是set()方法的一系列操作,當get()時候,也是通過這樣得到i,進而取到table[i]裡面的Entry.
				所以如果我們把當前的ThreadLocal物件設為null,就得到不i值了,就可能會造成記憶體洩露。
			*/
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
		/**
				Entry 類繼承了弱引用,這個弱引用指向的是當前的這個ThreadLocal物件。
				所以ThreadLocal有記憶體洩露的可能,這個分析在接下來的圖中進行分析。
				
			*/
	 static class Entry extends WeakReference<ThreadLocal> {

			Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
		
		
	 
	 }
}

上面是ThreadLocal的原始碼中擷取的一部分,接下來我用圖分析一下。
首先我們建立一個
//粗略程式碼
ThreadLocal<Connection> threadL=new ThreadLocal<Connection>();
Connection conn = new Connection();//模擬建立
thread.set(conn);

橘黃色部分是每個執行緒私有的,也就是每個執行緒在java堆內建立的物件,白色部分為所有執行緒共有的。 當一個執行緒第一次操作ThreadLocal時(也就是所謂的ThreadLocal物件例項的set()方法),首先會在堆記憶體中建立橘黃色中顯示的一系列的物件。其中白色箭頭的意思是弱引用,在table陣列到Enter例項物件這一步,他是根據當前的TreadLocal例項中的一個threadLocalHashCode變數來得到 i 的值,進而取到table[i]對應的Enter例項,進而去得到Enter例項西面的Connection例項。 當我們把threadL置為空時。意思是堆中分配的ThreadLocal物件失去了強引用,因為Enter物件對ThreadLocal例項是軟引用,所以當垃圾回收時就會回收ThreadLocal例項, 此時要再獲取Conncetion例項時,因為ThreadLocal物件例項已找不到,所以就得不到上面所說的threadLocalHashCode得值,進而得不到table陣列的下標,所以有可能造成記憶體洩露。 雖然JAVA對ThreadLocal這個物件在呼叫get()和set()方法時候會進行一系列的清除工作,但是當這個執行緒執行完畢後,我們把Connection物件置為null,此時這個執行緒回到執行緒池中,並不清除。以後這個執行緒不會再執行ThreadLocal的一系列操作,但是這個執行緒的threadLocal變數還存在。所以這個時候就會造成記憶體洩露。

相關推薦

java執行單個物件共享一些方式

public class VisibilityTest { private static boolean flag; public static void main(String[] args) throws InterruptedException { new Thread(new E

Java執行--AtomicReference原子物件

AtomicReference<T>原子物件 AtomicReference<T>作用 是對”物件”T進行原子操作.  AtomicReference<T>的原始碼剖析 public class AtomicR

java 執行間通訊的幾種方式

1.如何讓兩個執行緒依次執行 假設有兩個執行緒,一個執行緒A,一個執行緒B,兩個執行緒分別依次列印 1-3 三個數字即可。 package Test;/** /** * @author Administrator * @createdate 2017-10-10 */ pu

JAVA執行間通訊的幾種方式

先抄錄下來慢慢看吧 今天在群裡面看到一個很有意思的面試題: “編寫兩個執行緒,一個執行緒列印125,另一個執行緒列印字母AZ,列印順序為12A34B56C……5152Z,要求使用執行緒間的通訊。” 這是一道非常好的面試題,非常能彰顯被面者關於多執行緒的功力,一下

java執行阻塞喚醒的四種方式

java在多執行緒情況下,經常會使用到執行緒的阻塞與喚醒,這裡就為大家簡單介紹一下以下幾種阻塞/喚醒方式與區別,不做詳細的介紹與程式碼分析 suspend與resume Java廢棄 sus

Java--如何使用多執行一個HashSet進行平行計算

這段時間工作比較忙。今天抽空整理了一個多執行緒使用場景。 當處理一個數據量比較大的集合時(每個元素的計算都耗時比較長)。由於只使用一個執行緒進行計算比較慢。所以想到多跑幾個執行緒進行處理。 1.每個執行緒可以自行計算要處理集合的開始和結束索引,確保每一個元素都被計算的到。

Java執行執行安全,開啟多執行及每執行迴圈10次類進行輸出測試

最近看到執行緒問題,emmm~腦闊回想到計算機作業系統貌似又講,不過上課睡覺覺去啦哈哈哈,java課老師莫得講~ 然歸正傳,今對執行緒進行查閱及測試,做一下筆記,有錯之處還請指出,謝謝~上程式碼之前呢先說一哈前傳 執行緒是程序中的最小執行單位:    手機呢會有很多單獨

Java執行基礎之資料共享引發的“非執行安全”

例項變數與執行緒安全      自定義執行緒類中的例項變數針對其他執行緒可以有共享與不共享之分,這在多個執行緒之間進行互動時是很重要的一個技術點。     一 、不共享資料的情況        

Java執行同步的認識

synchronized關鍵字 Java以提供關鍵字synchronized的形式,以防止多執行緒時資源衝突提供了內建支援。當任務要執行被synchronized關鍵字保護的程式碼片段時,它將檢查鎖是否可用,然後獲取鎖,執行程式碼,釋放鎖。 所有物件都自動含

Java執行程式設計-(7)-使用執行池實現執行的複用和一些坑的避免

原文出自 : https://blog.csdn.net/xlgen157387/article/details/78253096 執行緒複用:執行緒池 首先舉個例子: 假設這裡有一個系統,大概每秒需要處理5萬條資料,這5萬條資料為一個批次,而這沒秒傳送的5萬條資料

Java執行--多個物件多個鎖

上一篇部落格中介紹了多個執行緒同時訪問一個物件,產生一個物件鎖,屬於同步訪問,現在介紹下如果是訪問多個物件,會怎麼執行那? Demo: HasSelfPrivateNum類: public class HasSelfPrivateNum { pri

Java執行同步:synchronized鎖住的是程式碼還是物件

在Java中,synchronized關鍵字是用來控制執行緒同步的,就是在多執行緒的環境下,控制synchronized程式碼段不被多個執行緒同時執行。synchronized既可以加在一段程式碼上,也可以加在方法上。 關鍵是,不要認為給方法或者程式碼段加上synchron

JAVA執行物件的狀態

JAVA執行緒物件的狀態 1.概述 在Thread類中定義了執行緒的6種狀態:新建態,就緒態、執行態,阻塞態、等待態、終止態。 1)新建態:使用new運算子建立一個執行緒物件(new Thread)後,該執行緒僅僅是一個空物件,系統沒有為它分配資源,該執行緒處於新建態(NEW

4個執行例子,2個執行同一數字加法運算另外2個執行同一共享數字減法運算

package com.threeti.mecool.web; //具體業務加減方法 public class AddJian {public int i=0;public synchronized void add(String threadName) {i++;Syst

Java執行基礎之物件鎖的同步與非同步

同步:synchronized 同步的概念就是共享,如果不是共享的資源,就沒有必要進行同步。 非同步:asynchronized 非同步的概念就是獨立,相互之間不受到任何制約。 同步的目的就是為了執行緒安全,對於

Java執行中的物件互斥鎖

1、為什麼會有鎖? 在看執行緒同步的問題之前,我們先看一個生活中的小例子: 我拿著銀行卡去ATM取錢,假如我的卡里有3000塊,我要取走2000,這個時候,ATM會去銀行的資料庫裡查詢我的賬戶是否有2000以上的餘額,如果有,就會讓我取走,不幸的是,這個時候

Java執行探究-Lock物件鎖條件變數

Lock鎖的條件變數 設想這樣的一種情況,現在有一個盤子,一個執行緒負責往盤子裡放一個蘋果,一個執行緒從盤子取一個蘋果,如何保證執行緒A放一個蘋果,執行緒B就把這個蘋果取了,不會出現已經放了好幾個了,執行緒B才一個一個的取,現在限定一個條件,盤子裡每次只

java執行資料共享問題總結

1.執行緒同步,一個關鍵字:synchronized 為什麼有這個東西呢,假如有一個物件,裡面有成員變數和方法,如果有很多執行緒都想訪問它們,有可能造成使用者想避免的結果。 我也舉那個經典的例子:假如你的銀行賬戶裡面有2000塊錢,有一天你去銀行櫃檯取錢,取1500,正在你

java 執行間資料共享和android 執行間資料共享異同

3 import org.apache.http.HttpResponse; 4 import org.apache.http.client.HttpClient; 5 import org.apache.http.client.methods.HttpGet; 6 import org.apac

Java執行安全與不安全的理解

  當我們檢視JDK API的時候,總會發現一些類說明寫著,執行緒安全或者執行緒不安全,比如說到StringBuilder中,有這麼一句,“將StringBuilder 的例項用於多個執行緒是不安全的。