成員變量與線程安全

經過集中狀況來觀察成員變量對線程安全的影響:
java

1.數據不共享

線程類代碼以下:
web

package com.feng.example;

public class MyThread extends Thread {

	private int count = 5;
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(count > 0)
		{
			count--;
			System.out.println(Thread.currentThread().getName()+"==="+count);
		}
	}	
}

測試類代碼以下數組

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		Thread a = new MyThread();
		Thread b = new MyThread();
		Thread c = new MyThread();

		a.start();
		b.start();
		c.start();
	}

}

分析:在主程序中分別建立了三個線程實例對象,三個實例對象有本身的內容空間,有本身的成員變量,內存模型以下圖所示:安全

三個線程a.b.c都有各自的count成員變量,三者運行互不影響。所以在數據不共享的狀況下是不會出現線程安全問題的。多線程

程序輸出:ide

Thread-0===4
Thread-0===3
Thread-0===2
Thread-0===1
Thread-0===0
Thread-1===4
Thread-1===3
Thread-1===2
Thread-1===1
Thread-1===0
Thread-2===4
Thread-2===3
Thread-2===2
Thread-2===1
Thread-2===0

2.數據共享

在數據共享這一部分分爲兩個部分來說:測試

(1)多個線程實例(同一個類的實例)的成員變量指向同一個對象,那就將成員變量改成static類型

線程類改寫爲:this

package com.feng.example;

public class MyThread extends Thread {

	private static int  count = 5;
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(count > 0)
		{
			count--;
			System.out.println(Thread.currentThread().getName()+"==="+count);
		}
	}	
}

測試類不變:spa

分析:三個實例對象的成員變量都是使用指向的同一個成員變量,內存結構以下圖所示:線程

三個線程修改的是同一個count變量,那麼執行結果就再也不是每個線程都會循環5次了。除此以外,當線程a執行到了count--時,cpu切換去執行線程b,線程一樣執行到count--而後輸出,就會出現輸出兩次3,而沒有結果4。這就出現了線程安全問題。其餘的線程修改了本線程中還未處理完的數據(這裏指的是輸出)。

輸出結果爲:

Thread-0===3
Thread-0===2
Thread-0===1
Thread-0===0
Thread-1===3

由結果能夠看出,Thread-0和Thread-1都輸出了3,而正確的結果應該是輸出4,3,2,1,0這幾個數組都有的

解決方案:能夠將方法定義爲同步方法,在方法前加synchronized關鍵字??這種解決固然不正確,由於三個線程實例是三個對象,方法級別的synchronized是對對象加鎖,因此對象各不相同所以在方法上加同步是沒有任何效果的。正確的作法是使用synchronized語句塊對MyThread的class文件加鎖,程序修改以下:

package com.feng.example;

public class MyThread extends Thread {

	private static int  count = 5;
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		synchronized(MyThread.class)
		{
			while(count > 0)
			{
				count--;
				System.out.println(Thread.currentThread().getName()+"==="+count);
			}
		}
	}	
}

測試類不修改,執行結果以下:

Thread-2===4
Thread-2===3
Thread-2===2
Thread-2===1
Thread-2===0

固然解決的方案有不少,這裏不細講解決方案。

有的書本中的講解都會說起count++, count--會被分紅三步操做的問題,這裏我我的認爲存在這一方面的緣由,但也存在count--後時間片到了的狀況,去執行其餘線程的代碼塊,致使了count的值不許確。驗證這一說法的辦法就是將count--修改成--count,--count課時寄存器自減操做不會分紅三步操做了吧。結果一樣會出現相同的值。這個驗證你們自行測試。

(2)不一樣的線程(不一樣的線程類)引用同一對象做爲成員變量

定義兩個線程MyThreadA,MyThreadB,自定義類MyNum用於計數

package com.feng.example;

public class MyNum {
	
	int count;

	public int getCount() {
		return count;
	}

	public void setCount(int count) {
		this.count = count;
	}
}
package com.feng.example;

public class MyThreadA extends Thread{

	private MyNum count;
	
	public MyThreadA(MyNum count)
	{
		this.count = count;
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(count.getCount()  >0)
		{
			count.setCount(count.getCount()-1);
			System.out.println(Thread.currentThread().getName()+"====="+count.getCount());
		}
	}

	
}
package com.feng.example;

public class MyThreadB extends Thread{

	private MyNum count;
	
	public MyThreadB(MyNum count)
	{
		this.count = count;
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(count.getCount()  >0)
		{
			count.setCount(count.getCount()-1);
			System.out.println(Thread.currentThread().getName()+"====="+count.getCount());
		}
	}

	
}

測試類:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		MyNum count = new MyNum();
		count.setCount(5);
		Thread a = new MyThreadA(count);
		Thread b = new MyThreadB(count);

		a.start();
		b.start();
	}

}

經過傳遞同一個對象給兩個線程,這兩個線程共用這一個計數器。由於沒有同步操做,這個線程執行還會出現線程安全問題。

輸出結果:

Thread-1=====3
Thread-0=====3
Thread-1=====2
Thread-0=====1
Thread-1=====0

解決方案:可使用LocalThread解決,也可使用sychronized解決,此處不細講。

若是此處將計數器簡單的使用Integer類型,觀察會有什麼不一樣?爲何?

經過以上實驗能夠得出,不論是同一個class文件產生的多個線程實例仍是多個class文件產生的多個線程實例,只要對同一個對象進行處理就會出現線程安全問題。

3.淺談web中的Servlet

Servlet是單例的,意思就是說無論多少個請求,若是請求的是同一個Servlet,那麼他們都會使用同一個Servlet對象。

若是不慎在Servlet中使用成員變量保存前臺傳輸過來的數據,那麼後臺數據將會產生錯亂(爲何?查看共享數據中的第二個例子,將MyNum想象成Servlet,使用count接收前臺的數據)。所以在Servlet中都是在doGet或者doPost方法中使用局部變量來接收前臺的數據,由於每次調用方法時,都會爲這次方法調用開闢空間,方法中的各個局部變量之間沒有影響。

所以在Servlet中不多使用成員變量。我將單獨列出一個模塊討論多線程和單例之間的關係,這裏就不深刻研究了。

用本身的話總結一下:線程安全問題就是指應該成爲原子操做的模塊沒有完整的執行。

相關文章
相關標籤/搜索