【多執行緒】例項變數(synchronized)與執行緒安全
一、例項變數與執行緒安全:
package cn.hncu.lang.thread_;
/**
* 專案名:例項變數和執行緒安全
* 時間 :2017-9-17 下午2:14:02
*/
/*
* 【例項變數與執行緒安全】:
* 1.自定義執行緒類中的例項變數指標對其他執行緒可以有共享與不共享之分,這在多個執行緒之間進行互動時是很重要的。
* 2.不共享資料的情況:—–OneThread
* 3.由下面結果看出,一共建立了三個執行緒,每個執行緒都有各自的count變數,每個執行緒是自己減少自己的count變數的值,
* 這樣的情況就是變數不共享,此示例並不存在多個執行緒訪問同一個例項變數的情況。
* 4.如何該共享資料?
*/
class OneThread extends Thread{
private int count = 6;
public OneThread(String name){
this.setName(name);
}
@Override
public void run() {
while(count>0){
count--;
System.out.println("由"+this.currentThread().getName()+
"計算,count = " +count);
}
}
public static void main(String[] args) {
OneThread a = new OneThread("A");
OneThread b = new OneThread("B");
OneThread c = new OneThread("C");
a.start();
b.start();
c.start();
/*執行結果為:
由C計算,count = 5
由B計算,count = 5
由B計算,count = 4
由B計算,count = 3
由A計算,count = 5
由A計算,count = 4
由A計算,count = 3
由A計算,count = 2
由B計算,count = 2
由C計算,count = 4
由B計算,count = 1
由A計算,count = 1
由B計算,count = 0
由C計算,count = 3
由C計算,count = 2
由C計算,count = 1
由C計算,count = 0
由A計算,count = 0
*/
}
}
/*
* 【例項變數與執行緒安全】:
* 1.共享資料的情況:—-TwoThread
* 2.我們想讓多個執行緒操作同一個資料,並且每個執行緒處理後的結果值都是不同的。
* 3.好比就是投票:A投了一票count變為6,B投了一票count變為7,C投了一票count變為8,
* 此時如果A還想投一票,則count變為9。
*
* 4.但是此時執行結果和預想的不相同,B、A、C結果都為2,存在相同的結果值。
* 5.這就說明資料沒有共享,產生了“非執行緒安全”的問題。
* 6.但是這是什麼原因呢?
* 因為在某些JVM中,i–的操作分成如下三步:(1)取得原有i值;(2)計算i-1;(3)對i進行賦值。
* 在這三個步驟中,如果有多個執行緒同時訪問,那麼一定會出現非執行緒安全的問題。
*
* 7.如何處理這個問題呢?—-加鎖 synchronized
* 要求:在某個執行緒將上面的這三個步驟執行完以後,其他的執行緒才可以操作i,不然,其他執行緒一直在等待這個執行緒。
* 此時就會用到一個關鍵字來執行這一個操作:synchronized
* 8.這時就需要使多個執行緒之間進行同步,也就是用按順序排隊方式進行減1操作。
*
*/
class TwoThread extends Thread{
private int count = 5;
/*版本1:
@Override
public void run() {
count--;
System.out.println("由"+this.currentThread().getName()+
"計算,count = "+count);
}*/
/*版本2:為這個run方法加鎖,讓當前物件將這個run方法執行完畢以後才解鎖。
* 【synchronized說明】:
* 1.通過在run方法前加入synchronized關鍵字,使多個執行緒在執行run方法時 ,以排隊的方式進行處理。
* 2.當一個執行緒呼叫run前,先判斷run方法有沒有被上鎖,如果上鎖,說明有其他執行緒正在呼叫run方法,必須等
* 其他執行緒對run方法呼叫結束後才可以執行run方法。這樣也就實現了排隊呼叫run方法的目的,也就達到了按順序
* 對count變數減1的效果了。
* 3.synchronized可以在任意物件及方法上加鎖,而加鎖的這段程式碼稱為“互斥區”或“臨界區”。
* 4.當一個執行緒想要執行同步方法裡面的程式碼時,執行緒首先嚐試去拿著把鎖,若果能夠拿到這把鎖,
* 那麼這個執行緒就可以執行synchronized裡面的程式碼。如果不能拿到這把鎖,那麼這個執行緒就
* 會不斷的嘗試拿這把鎖,直到能夠拿到為止,而且是多個執行緒同時去爭搶這把鎖。
*
*/
@Override
synchronized public void run() {
count--;
System.out.println("由"+this.currentThread().getName()+
"計算,count = "+count);
}
public static void main(String[] args) {
TwoThread twoThread = new TwoThread();
Thread t1 = new Thread(twoThread, "A");
Thread t2 = new Thread(twoThread, "B");
Thread t3 = new Thread(twoThread, "C");
Thread t4 = new Thread(twoThread, "D");
Thread t5 = new Thread(twoThread, "E");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
/*版本1執行結果:
由B計算,count = 2
由A計算,count = 2
由E計算,count = 0
由C計算,count = 2
由D計算,count = 1
版本2執行結果:
由A計算,count = 4
由E計算,count = 3
由D計算,count = 2
由C計算,count = 1
由B計算,count = 0
*/
}
}
public class InstanceVariableAndThreadSafe {
public static void main(String[] args) {
}
}
/*
* 【非執行緒安全】:
* 1.非執行緒安全主要是指多個執行緒對同一個物件中的同一個例項變數進行操作時會出現
* 值被更改、值不同步的情況,進而影響程式的執行流程。
*
* 【說明】對於LoginServlet中的doPost方法的說明:
* 1.兩個執行緒同時訪問同一個類中的靜態方法,因此doPost的操作是:
* 2.doPost中傳入兩個引數,首先usernameRef = username;賦值;
* 3.賦值以後,如果:username.equals(“a”),則當前執行緒睡眠,其他執行緒進入此doPost方法;
* 4.首先usernameRef = username;賦值;如果username.equals(“a”)不成立,則
* 5.passwordRef = password;,此時輸出當前傳入的兩個引數,usernameRef、password
* 6.最後,另外一個執行緒開始執行,開始執行: passwordRef = password;,
* 7.此時輸出當前傳入的兩個引數,usernameRef、password,但是usernameRef已被另一個執行緒改掉。
B username = a, password = bb
A username = a, password = aa
加鎖以後:
A username = a, password = aa
B username = b, password = bb
*
*/
class LoginServlet{
private static String usernameRef;
private static String passwordRef;
synchronized public static void doPost(String username,String password){
try {
usernameRef = username;
if(username.equals("a")){
Thread.sleep(5000);
}
passwordRef = password;
System.out.println(Thread.currentThread().getName()+" username = "
+usernameRef+", password = "+password);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ALogin extends Thread{
@Override
public void run() {
this.setName("A");
LoginServlet.doPost("a", "aa");
}
}
class BLogin extends Thread{
@Override
public void run() {
this.setName("B");
LoginServlet.doPost("b", "bb");
}
}
class Run{
public static void main(String[] args) {
ALogin a = new ALogin();
BLogin b = new BLogin();
a.start();
b.start();
}
}