(四)多執行緒說學逗唱:執行緒險惡,變數和執行緒安全不得不防
阿新 • • 發佈:2018-12-08
- (一)多執行緒說學逗唱:關於執行緒那不得不說的二三事
- (二)多執行緒說學逗唱:新手村偶遇Thread類
- (三)多執行緒說學逗唱:村口的老R頭是個掃地僧(Runnable)
- 出了新手村,以後的路可就不那麼好走了,到底現在也是個江湖人,都必須經歷點困難挫折,要不以後拿什麼在酒桌上吹牛?Java是一門面向物件的語言,一個類有自己的成員變數和成員方法,可以是公共的(public),可以是私有的(private),也可以是受保護的(protected),管理還這些東西也是行走江湖的必備技能。
不共享資料與共享資料
- 在多執行緒環境中,例項變數針對其他執行緒存在共享和不共享之分,他們的情況如下圖:
- 首先來看一看不共享資料,它的特點就是存在多個數據源,每一個執行緒都使用針對的資料,執行緒之間的佔用的資源不會互相影響,反觀共享資料,只有一個數據源,多個執行緒之間均使用的是同一個資料。在接下來的程式碼示例中,我將會用兩個例子向大家展示這兩種資料形式的差別。
package day01; /** * * @ClassName: NoShareInstanceVariable * @Description: 不共享例項變數學習 * @author Jian.Wang * @date 2018年12月6日 * */ public class NoShareInstanceVariable { public static void main(String[] args) { try { NoShareVariable noShareVariable_a = new NoShareVariable("A"); NoShareVariable noShareVariable_b = new NoShareVariable("B"); NoShareVariable noShareVariable_c = new NoShareVariable("C"); noShareVariable_a.start(); noShareVariable_b.start(); noShareVariable_c.start(); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * * @ClassName: NoShareVariable * @Description: 不共享例項變數測試類 * @author Jian.Wang * @date 2018年12月6日 * * synchronized 標識為“互斥區”或“臨界區”,區中的程式碼意思上為加上了鎖,任何執行緒訪問臨界區的程式碼都需要先拿到鎖..... */ class NoShareVariable extends Thread{ private int count = 5; public NoShareVariable(String name) { super(); this.setName(name); } @SuppressWarnings("static-access") @Override public void run() { while (count > 0) { count--; System.out.println("由:" + this.currentThread().getName() + "計算,count=" + count); } } }
- NoShareVariable 類繼承了Thread類,重寫了run方法,無疑他是一個執行緒類物件,在run()方法中對於count每一次迴圈減一,在輸出當前的值。NoShareInstanceVariable 類中建立了三個NoShareVariable物件的實體,並且開啟了三個執行緒。很明顯這三個執行緒都有各自的count變數,自己減少自己的count值,這樣的情況就是變數不共享,各自為政,自立山頭,他的輸出結果為:
package day01; /** * * @ClassName: NoShareInstanceVariable * @Description: 不共享例項變數學習 * @author Jian.Wang * @date 2018年12月6日 * */ public class NoShareInstanceVariable { public static void main(String[] args) { try { ShareVariable shareVariable = new ShareVariable(); Thread shareVariable_a = new Thread(shareVariable,"A"); Thread shareVariable_b = new Thread(shareVariable,"B"); Thread shareVariable_c = new Thread(shareVariable,"C"); Thread shareVariable_d = new Thread(shareVariable,"D"); Thread shareVariable_e = new Thread(shareVariable,"E"); Thread shareVariable_f = new Thread(shareVariable,"F"); shareVariable_a.start(); shareVariable_b.start(); shareVariable_c.start(); shareVariable_d.start(); shareVariable_e.start(); shareVariable_f.start(); } catch (Exception e) { e.printStackTrace(); } } } /** * * @ClassName: ShareVariable * @Description: 共享例項變數類 * @author Jian.Wang * @date 2018年12月6日 * */ class ShareVariable extends Thread{ private int count = 5; @SuppressWarnings("static-access") @Override public void run() { super.run(); count--; System.out.println("由:" + this.currentThread().getName() + "計算,count=" + count); } }
- ShareVariable類中定義了一個私有變數count,在run方法中不斷自減,在NoShareInstanceVariable 中建立了一個實體,並作為引數傳遞到Thread中,建立了若干個執行緒。此時count資料就被建立的所有執行緒共享,其最後編碼的結果為:
- 上面的輸出結果分別遞減,也就是按照猜想所有執行緒共享一個count。但是細心的朋友會發現,F和E執行緒都訪問了-1這個值,這又是為什麼呢?原因就是CPU的速度是非常快的,這兩個執行緒在幾乎同一個時間訪問了count這個值,在某一個執行緒還沒有執行減操作是就也進行了訪問,因此造成了同時輸出兩個一樣的值,那麼這種情況該怎麼解決呢?這就要用到我們接下來就要講到的東西,處理執行緒安全問題。
synchronized處理執行緒安全
- 上面的情況要解決,只需要在run方法前加上synchronized關鍵字即可。
- 通過run方法前加synchronized關鍵字,使得多個執行緒在執行run方法的時候,以排隊的方式進行處理。當一個執行緒呼叫run方法前,先判斷run方法有沒有被上鎖,如果上鎖,說明有其他的執行緒正在呼叫run方法,必須等待其他執行緒對run方法地哦啊用結束以後才可以執行run方法。這樣也就實現了排隊呼叫run方法的目的。synchronized可以在任意物件及方法上加鎖,而加鎖的這段程式碼稱為“互斥區”或“臨界區”。當一個執行緒想要執行同步方法中的程式碼時,執行緒必須首先嚐試拿到這把鎖,如果能夠拿到這把鎖就能夠執行synchronized裡面的程式碼。如果拿不到這把鎖,那麼執行緒就會不斷地嘗試拿這把鎖,知道拿到為止,而且是同時有多個執行緒同時去爭搶這把鎖。下面就通過一個簡單的例子來展示這一場景,寫一個LoginServlet類,建立doPost方法模擬登陸業務。A和B兩個登入員分別為兩個不同的執行緒,呼叫doPost方法,看看在非執行緒安全和執行緒安全下展示出來不同的現象。
package day01;
import java.util.Objects;
/**
*
* @ClassName: NonThreadSafe
* @Description: 模擬非執行緒安全,並解決
* @author Jian.Wang
* @date 2018年12月6日
*
*/
public class NonThreadSafe {
public static void main(String[] args) {
ALogin aLogin = new ALogin();
aLogin.start();
BLogin bLogin = new BLogin();
bLogin.start();
}
}
/**
*
* @ClassName: LoginServlet
* @Description: 模擬登陸類,包含登陸方法
* @author Jian.Wang
* @date 2018年12月6日
*
*/
class LoginServlet {
private static String userNameRef;
private static String passwordRef;
public static void doPost(String userName, String password) {
try {
userNameRef = userName;
if (Objects.equals(userName, "a")) {
Thread.sleep(3000);
}
passwordRef = password;
System.out.println("userName=" + userNameRef + ", password=" + passwordRef);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
*
* @ClassName: ALogin
* @Description: A登陸員
* @author Jian.Wang
* @date 2018年12月6日
*
*/
class ALogin extends Thread {
@Override
public void run() {
super.run();
LoginServlet.doPost("a", "aa");
}
}
/**
*
* @ClassName: BLogin
* @Description: B登陸員
* @author Jian.Wang
* @date 2018年12月6日
*
*/
class BLogin extends Thread {
@Override
public void run() {
super.run();
LoginServlet.doPost("b", "bb");
}
}
- doPost方法前沒有加入synchronized 關鍵字時,出現了結果混亂的情況。
- 因此我們將synchronized 加到doPost方法上之後,結果就正確了。
多說一句 synchronized 在很多常用類中基本都用到了
- 資料安全是應用程式中的重中之重,因此在實際的開發過程中,往往對於重要資料的安全,特別是共享資料的安全維護是必然的,因此一些提供的常用類中很多方法都是執行緒安全的,比如StringBuffer等,雖然執行效率低點,但是能夠保護資料,同志們在平時的學習過程中還是要多多注意,萬一在面試的時候被面試官問到了答不出來豈不是很尷尬…