經過集中狀況來觀察成員變量對線程安全的影響:
java
線程類代碼以下:
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
在數據共享這一部分分爲兩個部分來說:測試
線程類改寫爲: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課時寄存器自減操做不會分紅三步操做了吧。結果一樣會出現相同的值。這個驗證你們自行測試。
定義兩個線程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文件產生的多個線程實例,只要對同一個對象進行處理就會出現線程安全問題。
Servlet是單例的,意思就是說無論多少個請求,若是請求的是同一個Servlet,那麼他們都會使用同一個Servlet對象。
若是不慎在Servlet中使用成員變量保存前臺傳輸過來的數據,那麼後臺數據將會產生錯亂(爲何?查看共享數據中的第二個例子,將MyNum想象成Servlet,使用count接收前臺的數據)。所以在Servlet中都是在doGet或者doPost方法中使用局部變量來接收前臺的數據,由於每次調用方法時,都會爲這次方法調用開闢空間,方法中的各個局部變量之間沒有影響。
所以在Servlet中不多使用成員變量。我將單獨列出一個模塊討論多線程和單例之間的關係,這裏就不深刻研究了。
用本身的話總結一下:線程安全問題就是指應該成爲原子操做的模塊沒有完整的執行。