初識多線程之非線程安全
非線程安全問題主要指多個線程對同一個對象中的同一個實例變量進行操作時會出現值被更改、值不同步的情況。
實例變量共享造成的非線程安全
例如:在實現投票功能的設計時,多個線程同時處理同一個人的票數
創建:類MyThread
public class MyThread extends Thread{
private int count = 5;
@Override
public void run() {
super.run();
count--;
System.out.println("由"+this.currentThread().getName()+"計算,count= "+count);
}
}
類:MyThreadRun
public class MyThreadRun {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread a = new Thread(myThread,"A");
Thread b = new Thread(myThread,"B");
Thread c = new Thread(myThread,"C");
Thread d = new Thread(myThread,"D");
Thread e = new Thread(myThread,"E");
a.start();
b.start();
c.start();
d.start();
e.start();
}
}
運行MyThreadRun類:
得到如圖所示的運行結果
由A計算,count= 3
由C計算,count= 2
由B計算,count= 3
由E計算,count= 0
由D計算,count= 1
這一個運行結果說明了以下幾個點:
1.線程是隨機性的,代碼的上下順序是不會影響到線程的執行順序的
2.執行start()的順序不代表執行run()的順序
3.多個線程同時訪問一個實例變量,那麼很大概率會出現非線程安全問題
實例變量共享造成的非線程安全問題的解決辦法 : 多個線程之間進行同步操作,即用按順序排隊的方式進行操作
更改MyThread類的代碼如下:
public class MyThread extends Thread{
private int count = 5;
@Override
synchronized public void run() { --->//使用synchronized 關鍵詞對執行count的run方法進行上鎖
super.run();
count--;
System.out.println("由"+this.currentThread().getName()+"計算,count= "+count);
}
}
Servlet技術造成的非線程安全以及解決辦法
創建類LoginServlet,模擬
public class LoginServlet {
private static String usernameRef;
private static String passwordRef;
public static void doPost(String username,String password){
try {
usernameRef = username;
if("a".equals(username)){
Thread.sleep(5000);
}
passwordRef = password;
System.out.println("username="+usernameRef+",password="+password);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
創建ALogin類:
public class ALogin extends Thread{
@Override
public void run(){
LoginServlet.doPost("a","aa");
}
}
窗邊BLogin類:
public class BLogin extends Thread{
@Override
public void run(){
LoginServlet.doPost("b","bb");
}
}
現在寫一個測試類,來驗證一下運行結果:
public class RunTest {
public static void main(String[] args) {
ALogin a = new ALogin();
a.start();
BLogin b = new BLogin();
b.start();
}
}
現在的運行結果爲,很明顯數據有錯誤,username爲b的密碼變成了a用戶的密碼
username=b,password=bb
username=b,password=aa
分析一下造成這種結果的原因:
- ALogin線程執行了doPost方法,對username和password傳入值a和aa
- ALogin線程執行了將username的值賦給了usernameRef
- ALogin線程滿足if的條件,ALogin線程暫停運行5s
- 此時,Blogin線程執行了doPost方法,對username和password傳入了b和bb
- 由於LoginServlet是單例的,只存在一份usernameRef和passwordRef變量,所以ALogin線程對usernameRef的a值被Blogin線程的b所覆蓋,usernameRef值變成b
- 當Blogin線程執行到if時,不滿足條件,繼續執行,將passwordRef變成了bb
- Blogin線程執行輸出,輸出b和bb的值
- 5s之後,ALogin線程繼續向下執行,參數password的值是aa是綁定到當前線程的,所以不會被Blogin的bb所覆蓋,password將aa覆給passwordRef,但是usernameRef被Blogin覆蓋成了bb
- 所以最後ALogin線程的輸出語句爲usernameRef:b,【password:aa
servlet技術造成的非線程安全的解決辦法:synchronized關鍵字
修改代碼如下:
public class LoginServlet {
private static String usernameRef;
private static String passwordRef;
synchronized public static void doPost(String username,String password){
try {
usernameRef = username;
if("a".equals(username)){
Thread.sleep(5000);
}
passwordRef = password;
System.out.println("username="+usernameRef+",password="+password);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
最後的運行結果:
username=a,password=aa
username=b,password=bb
PS:在web開發中,servlet對象本身就是單例的,所以爲了不出現非線程安全問題,建議不要在servlet中出現實例變量。