目錄
前情回顧
1. 中斷一個(gè)線(xiàn)程
1.1 中斷的API
1.2 小結(jié)
2. 等待一個(gè)線(xiàn)程
?2.1 等待的API
3. 線(xiàn)程的狀態(tài)
3.1 貫徹線(xiàn)程的所有狀態(tài)
3.2 線(xiàn)程狀態(tài)和狀態(tài)轉(zhuǎn)移的意義
4.?多線(xiàn)程帶來(lái)的的風(fēng)險(xiǎn)-線(xiàn)程安全 (重點(diǎn))
4.1 觀察線(xiàn)程不安全
4.2 線(xiàn)程安全的概念
4.3 線(xiàn)程不安全的原因
4.3.1 修改共享數(shù)據(jù)
4.3.2 原子性
4.3.3 可見(jiàn)性
4.3.4 代碼順序性
4.4 解決之前的線(xiàn)程不安全問(wèn)題
前情回顧
操作系統(tǒng)、進(jìn)程和線(xiàn)程_木子斤欠木同的博客-CSDN博客
深入淺出Java的多線(xiàn)程編程——第一篇_木子斤欠木同的博客-CSDN博客
讓我們來(lái)回顧一下,第一篇多線(xiàn)程的內(nèi)容:
1. 多線(xiàn)程:
(1)線(xiàn)程的概念
(2)進(jìn)程和線(xiàn)程的區(qū)別
(3)Java代碼如何創(chuàng)建線(xiàn)程
①繼承Thread重寫(xiě)run
②實(shí)現(xiàn)Runnable接口重寫(xiě)run,將該實(shí)現(xiàn)類(lèi)作為參數(shù)傳給Thread的構(gòu)造方法
③繼承Thread,匿名內(nèi)部類(lèi)
④實(shí)現(xiàn)Runnable,匿名內(nèi)部類(lèi)
⑤lambda表達(dá)式
2. Thread的常用屬性
start方法,真正從系統(tǒng)這里,創(chuàng)建一個(gè)線(xiàn)程,新的線(xiàn)程將會(huì)執(zhí)行run方法。
run方法:表示線(xiàn)程的入口方法是啥(線(xiàn)程啟動(dòng)起來(lái),要執(zhí)行哪些邏輯)(run方法不是讓程序猿調(diào)用的,要交給系統(tǒng)去自動(dòng)調(diào)用)【換個(gè)角度理解:我們可以把線(xiàn)程的run方法理解為main方法,都是系統(tǒng)去自動(dòng)調(diào)用】
1. 中斷一個(gè)線(xiàn)程
李四一旦進(jìn)到工作狀態(tài),他就會(huì)按照行動(dòng)指南上的步驟去進(jìn)行工作,不完成是不會(huì)結(jié)束的。但有時(shí)我們需要增加一些機(jī)制,例如老板突然來(lái)電話(huà)了,說(shuō)轉(zhuǎn)賬的對(duì)方是個(gè)騙子,需要趕緊停止轉(zhuǎn)賬,那張三該如何通知李四停止呢?這就涉及到我們的停止線(xiàn)程的方式了。
本質(zhì)上來(lái)說(shuō),讓一個(gè)線(xiàn)程終止,辦法就一種,讓該線(xiàn)程的入口方法執(zhí)行完畢!也就是讓run跑完!
目前常見(jiàn)的有以下兩種方式:
- 通過(guò)共享的標(biāo)記來(lái)進(jìn)行溝通
- 調(diào)用 interrupt() 方法來(lái)通知【記住,只是通知而已】
示例1:使用自定義的變量來(lái)作為標(biāo)志位
- 需要給標(biāo)志位上加 volatile 關(guān)鍵字(這個(gè)關(guān)鍵字的功能后面介紹).
public static volatile boolean isQuit = false;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while (!isQuit){
System.out.println(Thread.currentThread().getName() + " : 別管我,我忙著轉(zhuǎn)正!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " : 啊!險(xiǎn)些誤了大事");
});
System.out.println(Thread.currentThread().getName() + " : 讓李四開(kāi)始轉(zhuǎn)賬");
t.start();
Thread.sleep(10*1000);
System.out.println(Thread.currentThread().getName() + " : 老板來(lái)電話(huà)了,得趕緊通知李四對(duì)方是個(gè)騙子!");
isQuit = true;
}
這里的? ?public static volatile boolean isQuit = false; 為什么需要加static,因?yàn)閙ain函數(shù)被static修飾,所以在main內(nèi)部用到的成員變量要加static
示例2:使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定義標(biāo)志位。
1.1 中斷的API
- Thread 內(nèi)部包含了一個(gè) boolean 類(lèi)型的變量作為線(xiàn)程是否被中斷的標(biāo)記。
方法 | 說(shuō)明 |
pubilc void interrupt() | 中斷對(duì)象關(guān)聯(lián)的線(xiàn)程,如果線(xiàn)程正在阻塞,會(huì)把線(xiàn)程喚醒,則以異常的方式通知,然后吧標(biāo)志位設(shè)置為true |
public static boolean interrupted() | 判斷當(dāng)前線(xiàn)程的中斷標(biāo)志位是否設(shè)置,調(diào)用后清除標(biāo)志位 |
public boolean isInterrupted() | 判斷對(duì)象關(guān)聯(lián)的線(xiàn)程的標(biāo)志位是否設(shè)置,調(diào)用后不清除標(biāo)志位 |
- 使用 thread 對(duì)象的 interrupted() 方法通知線(xiàn)程結(jié)束.
public class Thread4 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
while(!Thread.currentThread().isInterrupted()){
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();
}
}
我們可以發(fā)現(xiàn),調(diào)用t.interrupt方法的時(shí)候,線(xiàn)程并沒(méi)有真的結(jié)束,而是打印了個(gè)異常信息,又繼續(xù)執(zhí)行了
1.2 小結(jié)
interrupt方法的作用:
(1)設(shè)置標(biāo)志位為true
(2)如果該線(xiàn)程正在阻塞中(比如執(zhí)行了sleep)此時(shí)就會(huì)把阻塞狀態(tài)喚醒,通過(guò)拋出異常的方式讓sleep立即結(jié)束
注意:一個(gè)非常重要的問(wèn)題,當(dāng)sleep被喚醒的時(shí)候(只有sleep被喚醒才會(huì)重置標(biāo)志位),sleep自動(dòng)地把isInterrupted標(biāo)志位給清空了(true - > false),這導(dǎo)致下次循環(huán),循環(huán)仍然可以繼續(xù)執(zhí)行了~~
有的開(kāi)關(guān),是按下之后,就按下去了
有的開(kāi)關(guān),是按下去之后,自動(dòng)彈起(sleep就屬于這種)
一種極端的情況:
如果設(shè)置interrupt的時(shí)候,恰好sleep剛醒,這個(gè)時(shí)候趕巧了,執(zhí)行到下一輪循環(huán)條件就直接結(jié)束了。但是這種概率非常低,畢竟sleep的時(shí)間已經(jīng)占據(jù)了整個(gè)循環(huán)體的99.999%的時(shí)間了
如果需要結(jié)束循環(huán),就得在catch中搞個(gè)break
public class Thread4 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
while(!Thread.currentThread().isInterrupted()){
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
});
t.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();
}
}
小結(jié)一下:
如果sleep執(zhí)行的時(shí)候看到這個(gè)標(biāo)志位是false,sleep正常進(jìn)行休眠操作
如果當(dāng)前的標(biāo)志位為true
sleep無(wú)論是剛剛執(zhí)行還是已經(jīng)執(zhí)行了一般,都會(huì)觸發(fā)兩件事
(1)立即拋出異常
(2)清空標(biāo)志位為false?
再下次循環(huán),到sleep
由于當(dāng)前標(biāo)志位本身是false,就啥也不干~~
總結(jié)到這里,就有小伙伴有疑問(wèn)了,為什么sleep要清空標(biāo)志位呢?
目的就是為了讓線(xiàn)程自身能夠?qū)τ诰€(xiàn)程何時(shí)結(jié)束,有一個(gè)更明確的控制~~
當(dāng)前,interrupt方法,效果不是讓線(xiàn)程立即結(jié)束,而是告訴他,你該結(jié)束了,至于他是否真的要立即結(jié)束還是等會(huì)結(jié)束,都是由它本線(xiàn)程的代碼來(lái)控制~~,interrupt只是通知,而非“命令”!
有的朋友就好奇了,我如果不加sleep,這些能令線(xiàn)程阻塞的代碼,那是不是就能讓該線(xiàn)程直接結(jié)束呢?對(duì)的,是可以,但是工作中沒(méi)人會(huì)這么寫(xiě)代碼,一個(gè)不可控的線(xiàn)程是多么可怕!
這里就可以又引出一個(gè)問(wèn)題,java為啥不強(qiáng)制制定“命令結(jié)束”的操作呢?
只要調(diào)用interrupt就立即結(jié)束?
答:主要是設(shè)定成這種,對(duì)線(xiàn)程來(lái)說(shuō)非常不友好~,線(xiàn)程t何時(shí)結(jié)束,一定是t自己要最清楚,交給t自身來(lái)決定比較好!
2. 等待一個(gè)線(xiàn)程
有時(shí),我們需要等待一個(gè)線(xiàn)程完成它的工作后,才能進(jìn)行自己的下一步工作。例如,張三只有等李四轉(zhuǎn)賬成功,才決定是否存錢(qián),這時(shí)我們需要一個(gè)方法明確等待線(xiàn)程的結(jié)束。
public class Thread5 {
public static void main(String[] args) throws InterruptedException {
Runnable run = () -> {
for(int i = 0;i < 3;i++){
try {
System.out.println(Thread.currentThread().getName() + " : 我正在工作!");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " : 我的工作結(jié)束了!");
};
Thread t1 = new Thread(run,"李四");
Thread t2 = new Thread(run,"張三");
System.out.println("李四先工作");
t1.start();
t1.join();
System.out.println("李四做完了,張三開(kāi)始工作!");
t2.start();
t2.join();
System.out.println("王五做完了,張三開(kāi)始工作!");
System.out.println("兩人都工作結(jié)束了!");
}
}
在main線(xiàn)程中調(diào)用t1.join()表示main線(xiàn)程要等t1線(xiàn)程跑完,main線(xiàn)程才可以繼續(xù)執(zhí)行。
(1)main線(xiàn)程調(diào)用t1.join()的時(shí)候,如果t1還在運(yùn)行,此時(shí)main線(xiàn)程阻塞,知道t執(zhí)行完畢(t1的run執(zhí)行完了),main線(xiàn)程才從阻塞中解除,才繼續(xù)執(zhí)行。
(2)main線(xiàn)程調(diào)用t.join()的時(shí)候,如果t已經(jīng)結(jié)束了,此時(shí)join就不會(huì)阻塞,會(huì)立即執(zhí)行下去。
如果把兩個(gè)join方法注釋掉,就會(huì)CPU的搶占式調(diào)用的典型例子:
public class Thread5 {
public static void main(String[] args) throws InterruptedException {
Runnable run = () -> {
for(int i = 0;i < 3;i++){
try {
System.out.println(Thread.currentThread().getName() + " : 我正在工作!");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " : 我的工作結(jié)束了!");
};
Thread t1 = new Thread(run,"李四");
Thread t2 = new Thread(run,"張三");
System.out.println("李四先工作");
t1.start();
// t1.join();
System.out.println("李四做完了,張三開(kāi)始工作!");
t2.start();
// t2.join();
System.out.println("王五做完了,張三開(kāi)始工作!");
System.out.println("兩人都工作結(jié)束了!");
}
}
?2.1 等待的API
方法 | 說(shuō)明 |
public void join() | 等待線(xiàn)程結(jié)束 |
public void join(long millis) | 等待線(xiàn)程結(jié)束,最多等 millis 毫秒 |
public void join(long millis, int nanos) | 同理,但可以更高精度 |
3. 線(xiàn)程的狀態(tài)
3.1 貫徹線(xiàn)程的所有狀態(tài)
線(xiàn)程的狀態(tài)是一個(gè)枚舉類(lèi)型 Thread.State
public class Thread6 {
public static void main(String[] args) {
for (Thread.State state:Thread.State.values()) {
System.out.println(state);
}
}
}
- NEW: 安排了工作, 還未開(kāi)始行動(dòng)
- RUNNABLE: 可工作的. 又可以分成正在工作中和即將開(kāi)始工作.
- BLOCKED: 這幾個(gè)都表示排隊(duì)等著其他事情
- WAITING: 這幾個(gè)都表示排隊(duì)等著其他事情
- TIMED_WAITING: 這幾個(gè)都表示排隊(duì)等著其他事情
- TERMINATED: 工作完成了.
3.2 線(xiàn)程狀態(tài)和狀態(tài)轉(zhuǎn)移的意義
大家不要被這個(gè)狀態(tài)轉(zhuǎn)移圖嚇到,我們重點(diǎn)是要理解狀態(tài)的意義以及各個(gè)狀態(tài)的具體意思。
舉個(gè)栗子:
? ? ? ?剛把李四、王五找來(lái),還是給他們?cè)诎才湃蝿?wù),沒(méi)讓他們行動(dòng)起來(lái),就是 NEW 狀態(tài);
當(dāng)李四、王五開(kāi)始去窗口排隊(duì),等待服務(wù),就進(jìn)入到 RUNNABLE 狀態(tài)。該狀態(tài)并不表示已經(jīng)被銀行工。
? ? ? ?作人員開(kāi)始接待,排在隊(duì)伍中也是屬于該狀態(tài),即可被服務(wù)的狀態(tài),是否開(kāi)始服務(wù),則看調(diào)度器的調(diào)度;
? ? ? ?當(dāng)李四、王五因?yàn)橐恍┦虑樾枰ッΓ缧枰顚?xiě)信息、回家取證件、發(fā)呆一會(huì)等等時(shí),進(jìn)入
? ? ? ? BLOCKED 、 WATING 、 TIMED_WAITING 狀態(tài),至于這些狀態(tài)的細(xì)分,我們以后再詳解;
如果李四、王五已經(jīng)忙完,為 TERMINATED 狀態(tài)。所以,之前我們學(xué)過(guò)的 isAlive() 方法,可以認(rèn)為是處于不是 NEW 和 TERMINATED 的狀態(tài)都是活著的。
4.?多線(xiàn)程帶來(lái)的的風(fēng)險(xiǎn)-線(xiàn)程安全 (重點(diǎn))
本質(zhì)是因?yàn)榫€(xiàn)程之間的調(diào)度順序的不確定性
4.1 觀察線(xiàn)程不安全
public class Thread7 {
static int count = 0;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for(int i = 0;i < 5000;i++){
count++;
}
});
Thread t2 = new Thread(() -> {
for(int i = 0;i < 5000;i++){
count++;
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(count);
}
}
?
由于當(dāng)前這兩線(xiàn)程調(diào)度的順序是無(wú)序的~~
你也不知道這兩線(xiàn)程自增的過(guò)程中,到底經(jīng)歷了什么
有多少次是“順序執(zhí)行”,有多少次是“交錯(cuò)執(zhí)行”不知道!
得到的結(jié)果是啥也就是不確定的!
這里就引出了一個(gè)問(wèn)題,出現(xiàn)bug之后,得到的結(jié)果一定是 <= 10000,或者結(jié)果一定是 >= 5000?
CPU調(diào)用是以原語(yǔ)為單位的!
4.2 線(xiàn)程安全的概念
想給出一個(gè)線(xiàn)程安全的確切定義是復(fù)雜的,但我們可以這樣認(rèn)為:
如果多線(xiàn)程環(huán)境下代碼運(yùn)行的結(jié)果是符合我們預(yù)期的,即在單線(xiàn)程環(huán)境應(yīng)該的結(jié)果,則說(shuō)這個(gè)程序是線(xiàn)程安全的。
4.3 線(xiàn)程不安全的原因
4.3.1 修改共享數(shù)據(jù)
上面的線(xiàn)程不安全的代碼中, 涉及到多個(gè)線(xiàn)程針對(duì) counter.count 變量進(jìn)行修改.
此時(shí)這個(gè) count 是一個(gè)多個(gè)線(xiàn)程都能訪(fǎng)問(wèn)到的 "共享數(shù)據(jù)"!
count 這個(gè)變量就是在堆上. 因此可以被多個(gè)線(xiàn)程共享訪(fǎng)問(wèn).?
4.3.2 原子性
什么是原子性
我們把一段代碼想象成一個(gè)房間,每個(gè)線(xiàn)程就是要進(jìn)入這個(gè)房間的人。如果沒(méi)有任何機(jī)制保證,A進(jìn)入房間之后,還沒(méi)有出來(lái);B 是不是也可以進(jìn)入房間,打斷 A 在房間里的隱私。這個(gè)就是不具備原子性的。
那我們應(yīng)該如何解決這個(gè)問(wèn)題呢?是不是只要給房間加一把鎖,A 進(jìn)去就把門(mén)鎖上,其他人是不是就進(jìn)不來(lái)了。這樣就保證了這段代碼的原子性了。有時(shí)也把這個(gè)現(xiàn)象叫做同步互斥,表示操作是互相排斥的。?
一條 java 語(yǔ)句不一定是原子的,也不一定只是一條指令
比如剛才我們看到的 n++,其實(shí)是由三步操作組成的:
- 從內(nèi)存把數(shù)據(jù)讀到 CPU
- 進(jìn)行數(shù)據(jù)更新
- 把數(shù)據(jù)寫(xiě)回到 CPU
不保證原子性會(huì)給多線(xiàn)程帶來(lái)什么問(wèn)題
如果一個(gè)線(xiàn)程正在對(duì)一個(gè)變量操作,中途其他線(xiàn)程插入進(jìn)來(lái)了,如果這個(gè)操作被打斷了,結(jié)果就可能是錯(cuò)誤的。
這點(diǎn)也和線(xiàn)程的搶占式調(diào)度密切相關(guān). 如果線(xiàn)程不是 "搶占" 的, 就算沒(méi)有原子性, 也問(wèn)題不大。
4.3.3 可見(jiàn)性
可見(jiàn)性指一個(gè)線(xiàn)程對(duì)共享變量值的修改,能夠及時(shí)地被其他線(xiàn)程看到。
Java 內(nèi)存模型 (JMM): Java虛擬機(jī)規(guī)范中定義了Java內(nèi)存模型.
目的是屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪(fǎng)問(wèn)差異,以實(shí)現(xiàn)讓Java程序在各種平臺(tái)下都能達(dá)到一致的并發(fā)效果.
- 線(xiàn)程之間的共享變量存在 主內(nèi)存 (Main Memory).
- 每一個(gè)線(xiàn)程都有自己的 "工作內(nèi)存" (Working Memory) .
- 當(dāng)線(xiàn)程要讀取一個(gè)共享變量的時(shí)候, 會(huì)先把變量從主內(nèi)存拷貝到工作內(nèi)存, 再?gòu)墓ぷ鲀?nèi)存讀取數(shù)據(jù).
- 當(dāng)線(xiàn)程要修改一個(gè)共享變量的時(shí)候, 也會(huì)先修改工作內(nèi)存中的副本, 再同步回主內(nèi)存.?
由于每個(gè)線(xiàn)程有自己的工作內(nèi)存, 這些工作內(nèi)存中的內(nèi)容相當(dāng)于同一個(gè)共享變量的 "副本". 此時(shí)修改線(xiàn)程1 的工作內(nèi)存中的值, 線(xiàn)程2 的工作內(nèi)存不一定會(huì)及時(shí)變化.
1) 初始情況下, 兩個(gè)線(xiàn)程的工作內(nèi)存內(nèi)容一致.
2) 一旦線(xiàn)程1 修改了 a 的值, 此時(shí)主內(nèi)存不一定能及時(shí)同步. 對(duì)應(yīng)的線(xiàn)程2 的工作內(nèi)存的 a 的值也不一定能及時(shí)同步.?
這個(gè)時(shí)候代碼中就容易出現(xiàn)問(wèn)題.
此時(shí)引入了兩個(gè)問(wèn)題:?
- 為啥要整這么多內(nèi)存?
- 為啥要這么麻煩的拷來(lái)拷去?
1) 為啥整這么多內(nèi)存?
實(shí)際并沒(méi)有這么多 "內(nèi)存". 這只是 Java 規(guī)范中的一個(gè)術(shù)語(yǔ), 是屬于 "抽象" 的叫法.
所謂的 "主內(nèi)存" 才是真正硬件角度的 "內(nèi)存". 而所謂的 "工作內(nèi)存", 則是指 CPU 的寄存器和高速緩存.
2) 為啥要這么麻煩的拷來(lái)拷去?
因?yàn)?CPU 訪(fǎng)問(wèn)自身寄存器的速度以及高速緩存的速度, 遠(yuǎn)遠(yuǎn)超過(guò)訪(fǎng)問(wèn)內(nèi)存的速度(快了 3 - 4 個(gè)數(shù)量級(jí), 也就是幾千倍, 上萬(wàn)倍)
比如某個(gè)代碼中要連續(xù) 10 次讀取某個(gè)變量的值, 如果 10 次都從內(nèi)存讀, 速度是很慢的. 但是如果只是第一次從內(nèi)存讀, 讀到的結(jié)果緩存到 CPU 的某個(gè)寄存器中, 那么后 9 次讀數(shù)據(jù)就不必直接訪(fǎng)問(wèn)內(nèi)存了,效率就大大提高了
那么接下來(lái)問(wèn)題又來(lái)了, 既然訪(fǎng)問(wèn)寄存器速度這么快, 還要內(nèi)存干啥??
答案就是一個(gè)字: 貴
值的一提的是, 快和慢都是相對(duì)的. CPU 訪(fǎng)問(wèn)寄存器速度遠(yuǎn)遠(yuǎn)快于內(nèi)存, 但是內(nèi)存的訪(fǎng)問(wèn)速度又遠(yuǎn)遠(yuǎn)快于硬盤(pán)。
對(duì)應(yīng)的, CPU 的價(jià)格最貴, 內(nèi)存次之, 硬盤(pán)最便宜。
4.3.4 代碼順序性
什么是代碼重排序
一段代碼是這樣的:
- 去前臺(tái)取下 U 盤(pán)
- 去教室寫(xiě) 10 分鐘作業(yè)
- 去前臺(tái)取下快遞
如果是在單線(xiàn)程情況下,JVM、CPU指令集會(huì)對(duì)其進(jìn)行優(yōu)化,比如,按 1->3->2的方式執(zhí)行,也是沒(méi)問(wèn)題,可以少跑一次前臺(tái),這種叫做指令重排序。
編譯器對(duì)于指令重排序的前提是 "保持邏輯不發(fā)生變化". 這一點(diǎn)在單線(xiàn)程環(huán)境下比較容易判斷, 但是在多線(xiàn)程環(huán)境下就沒(méi)那么容易了, 多線(xiàn)程的代碼執(zhí)行復(fù)雜程度更高, 編譯器很難在編譯階段對(duì)代碼的執(zhí)行效果進(jìn)行預(yù)測(cè), 因此激進(jìn)的重排序很容易導(dǎo)致優(yōu)化后的邏輯和之前不等價(jià).文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-731038.html
重排序是一個(gè)比較復(fù)雜的話(huà)題, 涉及到 CPU 以及編譯器的一些底層工作原理, 此處不做過(guò)多討論文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-731038.html
4.4 解決之前的線(xiàn)程不安全問(wèn)題
public class Thread7 {
static int count = 0;
public static void main(String[] args) {
Object o = new Object();
Thread t1 = new Thread(() -> {
synchronized (o) {
for(int i = 0;i < 5000;i++){
count++;
}
}
});
Thread t2 = new Thread(() -> {
synchronized (o) {
for(int i = 0;i < 5000;i++){
count++;
}
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(count);
}
}
到了這里,關(guān)于深入淺出Java的多線(xiàn)程編程——第二篇的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!