新聞中心
多線程在多進程的基礎上更好解決了并發(fā)問題,但由于一個進程內的多個線程是資源共享的,就會出現多個線程在并發(fā)執(zhí)行的時候造成內存中數據的混亂。
舉一個例子:
class Counter {
public int count;
public void add() {
count++;
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new Thread(() ->{
for (int i = 0; i< 50000; i++) {
counter.add();
}
});
Thread t2 = new Thread(() ->{
for (int i = 0; i< 50000; i++) {
counter.add();
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("count = " + counter.count);
}
}
這里定義兩個線程實例對象t1,t2(執(zhí)行的任務分別是循環(huán)調用50000次add操作),add方法的作用是對成員變量count進行++,我們設想兩個線程并發(fā)執(zhí)行,結果輸出的count值應該為100000,但是并不是,這就是一個典型的線程安全問題?。?!
2.原因首先我們需要明確add操作主要干了什么?
可以看出 add操作分為了三個指令,沒進行一次++CPU就會執(zhí)行三條指令(其中這三條指令是串行的)
load :把內存中的值加載到CPU的寄存器上
add? :在CPU的寄存器上進行++操作
save: 把計算之后的值再存到內存當中
那為什么會在進行add操作的時候會出現輸出結果小于100000呢?
我們知道兩個線程在并發(fā)編程的時候是搶占式執(zhí)行的(誰先搶到CPU資源誰就會被優(yōu)先調度,此時另一個線程就會阻塞),此時兩個線程中的add操作鎖所對應的指令就會出現很多種情況,就會造成計算結果出錯。
這里舉出了兩個例子,兩個線程的所對應的三條指令順序都不一樣(是因為線程1,2搶占式執(zhí)行當某一個線程執(zhí)行其中一條指令時,另一個線程被調度時會優(yōu)先執(zhí)行另一個線程的指令,此時之前的線程就會被阻塞),第一種情況add了兩次正常輸出2,而第二種情況只被add了一次輸出1;
二,線程安全 1.概念什么是線程安全:線程安全確切的定義十分復雜,所以我們一般認為,如果多線程環(huán)境下代碼運行的結果是符合我們預期的,即在單線程環(huán)境應該的結果,則說這個程序是線程安全的;
上述例子若改成單線程:
class Counter2 {
public int count;
public void add() {
count++;
}
}
public class Test {
public static void main(String[] args) {
Counter2 counter2 = new Counter2();
for (int i = 0; i< 50000; i++) {
counter2.add();
}
for (int i = 0; i< 50000; i++) {
counter2.add();
}
System.out.println("count = " + counter2.count);
}
}
此時輸出正確,所以我們可以認為上述輸出結果不是預期的那種多線程代碼是線程不安全的。
2.原因1.搶占式執(zhí)行,隨機調度(根本原因,由操作系統(tǒng)內核決定,無法改變)
2.因為單個進程下的多個線程是資源共享的,所以多個線程修改同一個變量時會線程不安全
?多個線程修改不同的變量? 沒事
?一個線程修改同一個變量? 沒事
?多個線程讀取同一個變量? 沒事
3.原子性
?如果修改操作是原子性的,就不會有線程安全問題
?如果修改操作是非原子性的就很大概率出現線程安全問題(上述示例的add操作就是非原子性? ? ? ? ?的,一個add操作分成了三個指令去執(zhí)行)
?所以我們去避免線程安全的主要手段就是將非原子性的操作變成原子性---->加鎖
4.內存可見性問題
? 當一個線程在讀取數據,一個線程在修改數據時就會出現線程安全問題(也就是常說的臟讀問? ? ? ? 題)
5.指令重排序(本質上是代碼出現了bug)
三,synchronized關鍵字針對線程安全問題,我們往往常用的手段就是把非原子性操作變成原子性操作,此時就需要用到synchronized關鍵字(該關鍵字的作用就是對對象加鎖)
針對上述的代碼進行修改:
class Counter {
public int count;
synchronized public void add() {
count++;
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new Thread(() ->{
for (int i = 0; i< 50000; i++) {
counter.add();
}
});
Thread t2 = new Thread(() ->{
for (int i = 0; i< 50000; i++) {
counter.add();
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("count = " + counter.count);
}
}
此時輸出結果為我們預期的100000;
1.synchronized 的原理在多線程環(huán)境下,當一個線程在被調度時(拿上述例子解釋:當線程t1調用add方法進行conut++操作時,因為線程是搶占式執(zhí)行的,此時線程t2想要調用add方法時發(fā)現線程t1正在執(zhí)行add方法,線程t2就會發(fā)生線程阻塞,等待線程t1完全執(zhí)行完時線程t2才可以進行add操作),另一個線程就會阻塞等待,直到當前線程執(zhí)行完畢,另一個線程才可以執(zhí)行。
當有一個滑稽老鐵在上廁所時(廁所相當于對象,滑稽老鐵相當于線程),此時門就會上鎖(這個鎖的作用就相當于synchronized的作用),其余的滑稽老鐵必須等待當前的滑稽老鐵上完廁所,他們才可以使用這個廁所(相當于此時其他線程是阻塞等待的)。
2.synchronized 的特性1.互斥
指的是當一個線程執(zhí)行到synchronized對象中時,另一個對象執(zhí)行到這個對象時就會阻塞等待;
進入 synchronized 修飾的代碼塊, 相當于 加鎖
退出 synchronized 修飾的代碼塊 , 相當于 解鎖 2.可重入 synchronized 同步塊對同一條線程來說是可重入的,不會出現自己把自己鎖死的問題;
當同一個線程對一個對象多次加鎖時,并不會出現問題時就稱該鎖為可重入,否則就稱該鎖不可重入(此時會造成死鎖的問題)
3.synchronized的使用案例synchronized可以修飾方法(包括實例方法和靜態(tài)方法)也可以修飾代碼塊
修飾實例方法:鎖的是synchronized對象;
修飾靜態(tài)方法:鎖的是Counter類對象;
修飾代碼塊:鎖的是當前對象;
其中的this可以改成任意對象;
4.synchronized總結?如果兩個線程針對同一個對象進行加鎖,就會出現鎖競爭/鎖沖突,一個線程能夠獲取到鎖(先到先得)另一個線程阻塞等待,等待到上一個線程解鎖,它才能獲取鎖成功,否則就不會;
如果兩個線程針對不同對象加鎖,此時不會發(fā)生鎖競爭/鎖沖突,這倆線程都能獲取到各自的鎖,不會有阻塞等待了;
兩個線程,一個線程加鎖,一個線程不加鎖這個時候就不會有鎖競爭。
四,Java 標準庫中的線程安全類? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??線程不安全的集合類 | 線程安全的集合類 |
ArrayList | Vector ( 不推薦使用 ) |
LinkedList | HashTable ( 不推薦使用 ) |
HashMap | ConcurrentHashMap |
TreeMap | StringBuffer |
HashSet | |
TreeSet | |
StringBuilder |
雖然有的集合類是加鎖了,但是在使用時并不是建議無腦使用加鎖的集合類,因為加鎖也需要很多的時間開銷(根據情況進行選擇)
還有的雖然沒有加鎖,但是不涉及 "修改",?仍然是線程安全的:String
你是否還在尋找穩(wěn)定的海外服務器提供商?創(chuàng)新互聯www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準確流量調度確保服務器高可用性,企業(yè)級服務器適合批量采購,新人活動首月15元起,快前往官網查看詳情吧
網頁名稱:線程安全和synchronized關鍵字-創(chuàng)新互聯
網頁網址:http://biofuelwatch.net/article/djgdid.html