網(wǎng)站首頁 編程語言 正文
目錄
一. 線程安全問題
二. 你的多線程協(xié)調(diào)嗎 —— synchronized
1. 同步代碼塊
2. 同步方法
三. 什么是易變數(shù)據(jù) —— volatile
四. 要協(xié)調(diào)必須等待 —— wait方法
五. 你的線程協(xié)調(diào)得到通知了嗎 —— notify 或 notifyAll
? ? ? ?在多線程的程序中,有多個(gè)線程并發(fā)運(yùn)行,這多個(gè)并發(fā)執(zhí)行的線程往往不是孤立的,它們之間可能會(huì)共享資源,也可能要相互合作完成某一項(xiàng)任務(wù),如何使這多個(gè)并發(fā)執(zhí)行的線程在執(zhí)行的過程中不產(chǎn)生沖突,使多線程編程必須解決的問題。否則,可能導(dǎo)致線程運(yùn)行的結(jié)果不正確,甚至造成死鎖問題。
一. 線程安全問題
? ? ? ?在進(jìn)行多線程的程序設(shè)計(jì)時(shí),有時(shí)需要實(shí)現(xiàn)多個(gè)線程共享同一段代碼,從而實(shí)現(xiàn)共享同一個(gè)私有成員或類的靜態(tài)成員的目的。這時(shí),由于線程和線程之間爭搶CPU資源,線程無序地訪問這些共享資源,最終可能導(dǎo)致無法得到正確的結(jié)果。這些問題通常稱為線程安全問題。
? ? ? ?以火車站售票系統(tǒng)為例,在代碼中判斷當(dāng)前票數(shù)是否大于0,如果大于0則執(zhí)行將該票出售給乘客的功能,但當(dāng)兩個(gè)線程同時(shí)訪問這段代碼時(shí)(假如這時(shí)只剩下一張票),第一個(gè)線程將票售出,與此同時(shí)第二個(gè)線程也已經(jīng)執(zhí)行完成判斷是否有票的操作,并得出票數(shù)大于0的結(jié)論,于是它也執(zhí)行售出操作,這樣就會(huì)產(chǎn)生負(fù)數(shù)。所以,在編寫多線程程序時(shí),應(yīng)該考慮到線程安全問題。實(shí)質(zhì)上線程安全問題來源于兩個(gè)線程同時(shí)存取單一對(duì)象的數(shù)據(jù)。
? ? ? ?例如,在項(xiàng)目中創(chuàng)建ThreadSafeTest類,該類實(shí)現(xiàn)了Runnable接口,在未考慮到線程安全問題的基礎(chǔ)上,模擬火車站售票系統(tǒng)的功能的代碼如下:
public class ThreadSafeTest1 implements Runnable{
int num = 10;
@Override
public void run() {
while(true){
if(num > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->票數(shù)" + num--);
}
}
}
public static void main(String[] args) {
ThreadSafeTest1 t = new ThreadSafeTest1();
Thread t1 = new Thread("線程一");
Thread t2 = new Thread("線程二");
Thread t3 = new Thread("線程三");
Thread t4 = new Thread("線程四");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
-----------------------------------------------------------------------------------------
結(jié)果:
線程四-->票數(shù)10
線程三-->票數(shù)9
線程二-->票數(shù)8
線程一-->票數(shù)7
線程四-->票數(shù)6
線程三-->票數(shù)5
線程二-->票數(shù)4
線程一-->票數(shù)3
線程四-->票數(shù)2
線程三-->票數(shù)1
線程二-->票數(shù)-1
線程一-->票數(shù)0
線程四-->票數(shù)-2
? ? ? ?從這個(gè)結(jié)果可以看出,最后打印出的剩下的票數(shù)為負(fù)值,這樣就出現(xiàn)了問題。這是由于同時(shí)創(chuàng)建了4個(gè)線程,這4個(gè)線程執(zhí)行run()方法,在num變量為1時(shí),線程一、線程二、線程三、線程四都對(duì)num變量有存儲(chǔ)功能,當(dāng)線程一執(zhí)行run()方法時(shí),還沒有來得及做遞減操作,就指定它調(diào)用sleep()方法進(jìn)入就緒狀態(tài),這時(shí)線程二、線程三和線程四也都進(jìn)入了run()方法,發(fā)現(xiàn)num變量依然大于0,但此時(shí)線程一休眠時(shí)間已到,將num變量值遞減,同時(shí)線程二、線程三、線程四也都對(duì)num變量進(jìn)行遞減操作,從而產(chǎn)生了負(fù)值。
二. 你的多線程協(xié)調(diào)嗎 —— synchronized
1. 同步代碼塊
? ? ? ?那么,該如何解決資源共享的問題呢?所有解決多線程資源沖突問題的方法基本上都是采用給定時(shí)間只允許一個(gè)線程訪問共享資源的方法,這時(shí)就需要給共享資源上一道鎖。這就好比一個(gè)人上洗手間時(shí),他進(jìn)入洗手間后會(huì)將門鎖上,出來時(shí)再將鎖打開,然后其他人才可以進(jìn)入。
? ? ? ?Java中提供了同步機(jī)制,可以有效地防止資源沖突。同步機(jī)制使用synchronized關(guān)鍵字,使用該關(guān)鍵字包含的代碼塊稱為同步塊,也稱為臨界區(qū),語法如下:
synchronized(synObject){
//關(guān)鍵代碼
}
? ? ? ?通常將共享資源的操作放置在synchronized定義的區(qū)域內(nèi),這樣當(dāng)其他線程獲取到這個(gè)鎖時(shí),就必須等待鎖被釋放后才可以進(jìn)入該區(qū)域。Object為任意一個(gè)對(duì)象,每個(gè)對(duì)象都存在一個(gè)標(biāo)志位,并具有兩個(gè)值,分別為0和1。一個(gè)線程運(yùn)行到同步塊時(shí)首先檢查該對(duì)象的標(biāo)志位,如果為0狀態(tài),表明此同步塊內(nèi)存在其他線程,這時(shí)當(dāng)期線程處于就緒狀態(tài),直到處于同步塊中的線程執(zhí)行完同步塊中的代碼后,這時(shí)該對(duì)象的標(biāo)識(shí)位設(shè)置為1,當(dāng)期線程才能開始執(zhí)行同步塊中的代碼,并將Object對(duì)象的標(biāo)識(shí)位設(shè)置為0,以防止其他線程執(zhí)行同步塊中的代碼。
創(chuàng)建SynchronizedTest類,修改之前線程不安全的火車售票系統(tǒng),把對(duì)num操作的代碼設(shè)置在同步塊中。
public class ThreadSafeTest1 implements Runnable{
int num = 10;
@Override
public void run() {
while(true){
synchronized (this){
if(num > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->票數(shù)" + num--);
}
}
}
}
public static void main(String[] args) {
ThreadSafeTest1 t = new ThreadSafeTest1();
Thread t1 = new Thread("線程一");
Thread t2 = new Thread("線程二");
Thread t3 = new Thread("線程三");
Thread t4 = new Thread("線程四");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
-----------------------------------------------------------------------------------------
結(jié)果:
線程一-->票數(shù)10
線程一-->票數(shù)9
線程一-->票數(shù)8
線程一-->票數(shù)7
線程一-->票數(shù)6
線程一-->票數(shù)5
線程一-->票數(shù)4
線程一-->票數(shù)3
線程一-->票數(shù)2
線程一-->票數(shù)1
從這個(gè)結(jié)果可以看出,打印到最后票數(shù)沒有出現(xiàn)負(fù)數(shù),這是因?yàn)閷⒐蚕碣Y源放置在了同步塊中,不管程序如何運(yùn)行都不會(huì)出現(xiàn)負(fù)數(shù)。
2. 同步方法
? ? ? ?同步方法就是在方法前面用synchronized關(guān)鍵字修飾的方法,其語法如下:
public synchronized void Main();
? ? ? ? 當(dāng)某個(gè)對(duì)象調(diào)用了同步方法時(shí),該對(duì)象上的其他同步方法必須等待該同步方法執(zhí)行完畢后才能被執(zhí)行。必須將每個(gè)能訪問共享資源的方法修飾為synchronized,否則就會(huì)出錯(cuò)。
修改上面的代碼,將共享資源操作放置在一個(gè)同步方法中,代碼如下:
int num = 10;
public synchronized void doit(){
if(num > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->票數(shù)" + num--);
}
}
public void run(){
while(true){
doit();
}
}
三. 什么是易變數(shù)據(jù) —— volatile
volatile 的定義:
? ? ? ?java編程語言允許線程訪問共享變量,為了確保共享變量能被準(zhǔn)確和一致的更新,線程應(yīng)該確保通過排他鎖單獨(dú)獲得這個(gè)變量。Java語言提供了volatile,在某些情況下比鎖更加方便。如果一個(gè)字段被聲明成volatile,java線程內(nèi)存模型確保所有線程看到這個(gè)變量的值是一致的.
Volatile是輕量級(jí)的synchronized,輕量級(jí)體現(xiàn)在一下幾個(gè)方面:
- volatile?變量所需的編碼較少
- 運(yùn)行時(shí)開銷也較少
- 不會(huì)引起線程上下文的切換和調(diào)度(線程上下文即線程的運(yùn)行環(huán)境)
例1:
private volatile int num = 0;
private volatile String sum = nulll;
例2:假設(shè)多線程共同訪問一個(gè)數(shù)組,即許多線程對(duì)這個(gè)數(shù)組進(jìn)行排序,而同時(shí)其他線程打印這個(gè)數(shù)組的最小值和最大值。
...
static final int Size = 100;
static volatile int num[] = new int[Size];
static volatile int first = 0;
static volatile int last = 0;
static volatile boolean ready = false;
...
四. 要協(xié)調(diào)必須等待 —— wait方法
? ? ? ?wait() 方法和 notify() 或 notifyAll() 應(yīng)當(dāng)在 synchronized 的成程序塊或者方法中配合使用,使多線程在共享資源和數(shù)據(jù)時(shí)得到進(jìn)一步的保障。wait() 拋出檢查性異常 InterruptedException。在一個(gè)?synchronized 的代碼中調(diào)用 wait() 必須提供這個(gè)異常處理機(jī)制,例如:
try {
if (!ready){
wait();
...
}
}
...
? ? ? ?導(dǎo)致其他試圖進(jìn)入這個(gè)?synchronized 代碼中的線程放棄監(jiān)視器和鎖定,保證只有當(dāng)前線程執(zhí)行這段協(xié)調(diào)代碼。實(shí)際上,放棄鎖定的其他所以線程都進(jìn)入等待狀態(tài),直到某個(gè)在監(jiān)視器中運(yùn)行的線程調(diào)用?notify() 或者?notifyAll() ,例如:
if(ready){
notifyAll();
...
五. 你的線程協(xié)調(diào)得到通知了嗎 —— notify 或 notifyAll
? ? ? ?notify() 和?notifyAll() 必須和 wait() 配合使用。notify() 只是喚醒一個(gè)正在等待的線程。如果代碼中只有一個(gè)線程處于等待狀態(tài),調(diào)用?notify() 不存在問題。由于系統(tǒng)調(diào)度器處理線程調(diào)度安排的不透明性,喚醒哪個(gè)線程是不確定的。所以?notifyAll() 被經(jīng)常使用,以增強(qiáng)等待線程被通知的可靠性。
?
原文鏈接:https://blog.csdn.net/Lucky_mzc/article/details/124566148
相關(guān)推薦
- 2022-11-10 Linux實(shí)現(xiàn)壓縮文件的生成與查看的常用命令總結(jié)_linux shell
- 2022-07-31 一文理解Goland協(xié)程調(diào)度器scheduler的實(shí)現(xiàn)_Golang
- 2022-08-07 pd.drop_duplicates刪除重復(fù)行的方法實(shí)現(xiàn)_python
- 2022-09-26 車載藍(lán)牙PIN碼是什么
- 2022-09-15 Go語言中Goroutine的設(shè)置方式_Golang
- 2022-12-01 關(guān)于Linux之grep查找文本時(shí)匹配反斜杠\轉(zhuǎn)義問題_linux shell
- 2022-07-02 element下拉框獲取選中的內(nèi)容
- 2022-08-19 Spring Cloud Config配置服務(wù)
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支