執行緒安全(單例與多例)
又週五了,時間過得好快,住在集體宿舍,幾個宅男共處一室好是無聊,習慣性來到CSDN。今天一個應屆生同事突然問我為什麼老大要求我們不要在Service裡寫成員變數,說不安全,說為什麼不安全讓他自己去了解,看上去他沒有找到頭緒很是痛苦,想想當初這個問題也困擾過自己,向他解說的過程,順便寫下來和大家一起探討。老大說的好像對好像又不對,為什麼這樣說呢,因為我們專案的Service都是通過Spring去掃描,自然預設的都是單例,所以就有了執行緒安全問題。不在Service寫成員變數很簡單的就避免了執行緒不安全的情況,但一定要寫成員變數怎麼進行執行緒安全控制呢?這裡說的成員變數為非static, static成員變數後面另說。一是把Sevice在spring中配置成多例,從某種情況下避免了執行緒不安全的情況,還有一種方式是自寫程式碼進行執行緒安全控制。下面例出可能產生執行緒不安全的程式碼:
實體
public class Entity {
private int count;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
實體單例建立兩個執行緒public class Single { public static final Entity entity = new Entity(); public static Entity getEntity() { return entity; } }
public class ThreadA extends Thread { Entity entity = Single.getEntity(); public void run() { for (int i = 0; i < 5; i++) { entity.setCount(i); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("A" + entity.getCount()); } } }
public class ThreadB extends Thread {
Entity entity = Single.getEntity();
public void run() {
for (int i = 5; i < 10; i++) {
entity.setCount(i);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B" + entity.getCount());
}
}
}
入口
public class Test {
public static void main(String[] args) throws Exception {
new ThreadA().start();
new ThreadB().start();
}
}
可能產生的一種輸出結果是:B0
B6
A7
B1
B8
A9
B2
A2
A3
A4
回頭看我們的兩個執行緒中的for偱環,可能錯誤的認為A執行緒只會產生A0 A1 A2 A3 A4 , B執行緒只會產生B5 B6 B7 B8 B9, 但很顯然我們的輸出中出現了 B0 ,A7等你意料之外的資料,這裡就產生了執行緒不安全情況。我們把單例修改成多例試試,其它程式碼不變,只修改執行緒 A B的第一行程式碼:public class ThreadA extends Thread {
Entity entity = new Entity();
public void run() {
for (int i = 0; i < 5; i++) {
entity.setCount(i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("A" + entity.getCount());
}
}
}
public class ThreadB extends Thread {
Entity entity = new Entity();
public void run() {
for (int i = 5; i < 10; i++) {
entity.setCount(i);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B" + entity.getCount());
}
}
}
執行結果如下:
B5
A0
B6
B7
A1
B8
B9
A2
A3
A4
資料正確,已通過多例控制執行緒安全,這個僅是在非static成員變數的情況,在多例情況下如果實體中的成員變數為static,修改實體程式碼
public class Entity {
private static int count;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
執行結果如下:
B5
A6
B1
B7
A8
B2
B9
A9
A3
A4
又出現了A6等意外資料,要解決這種執行緒不安全問題可使用ThreadLocal, 相比synchronized個人比較喜歡ThreadLocal, synchronized排隊訪問,ThreadLocal建立變更副本,ThreadLocal通過空間換時間。修改實體
public class Entity {
private static ThreadLocal<Integer> count = new ThreadLocal<Integer>();
public int getCount() {
return count.get();
}
public void setCount(int count) {
this.count.set(count);
}
}
執行結果如下:
B5
A0
B6
B7
A1
B8
B9
A2
A3
A4
已執行緒安全化。(多例 static成員變數)
前面例子說到單例 非static成員變數的情況也會出現執行緒不安全,我們把執行緒A B第一行程式碼再次修改成
Entity entity = Single.getEntity();
實體的第一行程式碼 修改成非static成員變數
private ThreadLocal<Integer> count = new ThreadLocal<Integer>();
執行如下:B5
A0
B6
B7
A1
B8
B9
A2
A3
A4
已完成執行緒安全化。 單例 static成員變數也是執行緒不安全,一樣可以通過ThreadLocal執行緒安全化。
總之 單例 非static成員變數及static成員變數都是執行緒不安全, 多例 非static成員變數執行緒安全,但多例static成員變數會有執行緒不安全情況,
關於synchronized的實現還要考慮死鎖等情況,下回有時間再寫一下,本文有寫的不好不對的地方請指出,大家一起探討,謝謝!