Java中執行緒狀態、執行緒安全性問題和synchronized關鍵字的使用
java中的執行緒狀態
在作業系統層面,一個執行緒就兩個狀態:就緒和阻塞狀態.
但是java中為了在線程阻塞時能夠更快速的知曉一個線程阻塞的原因,又將阻塞的狀態進行了細化.
- ##NEW :線程物件已經創建好了,但是係統層面的線程還沒創建好,或者說線程對象還沒調用start()
- #TERMINATED:系統中的線程已經銷毀,但是程式碼中的執行緒物件還在,也就是run()跑完了,Thread物件還在
- RUNNABLE:執行緒位於就緒佇列,隨時都有可能被cpu調度執行
- TIMED_WAITING:執行緒執行過程中,執行緒物件呼叫了sleep(),進入阻塞,休眠時間到了,就會回到就緒佇列
- BLOCKED :有一個線程將一個對像上鎖(synchronized)之後,另一個線程也想給這個對像上鎖,就會陷入BLOCKED狀態,只有第一個線程將鎖對象解鎖了,後一個線程才有可能給這個物件進行上鎖.
- WAITING:搭配synchronized進行使用wait(),一旦一個執行緒呼叫了wait(),會先將所物件解鎖,等到另一個執行緒進行notify( ),之後wait中的執行緒才會被喚醒,當然也可以在wait()中設定一個最長等待時間,防止出現死等.
- 概念:一串程式碼什麼時候叫作有執行緒安全性問題呢?首先執行緒安全問題的罪惡之源是,多執行緒並發執行的時候,會有搶佔式執行的現象,這裡的搶佔式執行,執行的是機器指令!那一串代碼什麼時候叫作有線程安全問題呢?多線程並發時,不管若干個線程怎麼去搶佔式執行他們的程式碼,都不會影響最終結果,就叫作線程安全,但是由於搶佔式執行,出現了和預期不一樣的結果,就叫作有線程安全問題,出bug了!
- 典型案例:使用兩個執行緒對同一個數進行自增操作10w次:
public class Demo1 { private static int count=0; public static void main(String[] args) { Thread t1=new Thread(()->{ for(int i=0;i<50000;i++){ count++; } }); t1.start(); Thread t2=new Thread(()->{ t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(count); } } //打印结果:68994
分析原因:
僅針對每個線程的堆count進行自增的操作:首先要明白,進行一次自增的機器指令有三步:從主內存中把count值拿到cpu寄存器中->把寄存器中的count值進行自增1->把寄存器中的count值刷新到主內存中,我們姑且把這三步驟叫作:load->add->save我們假設就是在一個cpu上(畫兩個cpu好表示)並發執行兩組指令(就不會出現同時load這樣的情況了):那你可能會提問,那這樣和只用一個main線程去計算自增10w次有什麼區別,創建多線程還有什麼意義呢?
意义很大,因为我们创建的线程很多时候不仅仅只是一个操作,光针对自增我们可以通过加锁防止出现线程安全问题,但是各线程的其他操作要是不涉及线程安全问题那就可以并发了呀,那此时不就大大提升了执行效率咯.
4.具体如何加锁呢?
此处先只说一种加锁方式,先把上述案例的问题给解决了再说.
使用关键字synchronized,此处使用的是给普通方法加synchronized修饰的方法(除此之外,synchronized还可以修饰代码块和静态方法)
class Counter{ private int count; synchronized public void increase(){ this.count++; } public int getCount(){ return this.count; } } public class Demo2 { private static int num=50000; public static void main(String[] args) { Counter counter=new Counter();//此时对象中的count值默认就是0 Thread t1=new Thread(()->{ for (int i = 0; i < num; i++) { counter.increase(); } }); t1.start(); Thread t2=new Thread(()->{ for (int i = 0; i < num; i++) { counter.increase(); } }); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(counter.getCount()); } }//打印10W
内存可见性问题
首先说明:这是有编译器优化导致的,其次要知道cpu读取变量时:先从主内存将变量的值存至缓存或者寄存器中,cpu计算时再在寄存器中读取这个值.
当某线程频繁的从内存中读取一个不变的变量时,编译器将会把从内存获取变量的值直接优化成从寄存器直接获取.之所以这样优化,是因为,cpu从主内存中读取一个变量比在缓存或者寄存器中读取一个变量的值慢成千上万倍,如果每每在内存中读到的都是同一个值,既然缓存里头已经有这个值了,干嘛还大费周折再去主内存中进行获取呢,直接从缓存中直接读取就可以了,可提升效率.
但是:一旦一个线程被优化成上述的情况,那如果有另一个线程把内存中的值修改了,我被优化的线程还傻乎乎的手里拿着修改之前的值呢,或者内存中的变量值被修改了,被优化的线程此时已经感应不到了.
具体而言:
public class Demo3 { private static boolean flag=false; public static void main(String[] args) { Thread t1=new Thread(()->{ while(!flag){ System.out.println("我是优化完之后直接读取寄存器中的变量值才打印的哦!"); } }); t1.start(); flag=true; System.out.println("我已经在主线程中修改了标志位"); } }
运行上述代码之后,程序并不会终止,而是一直在那打印t1线程中的打印语句.
如何解决上述问题:
引入关键字volatile:防止内存可见性问题,修饰一个变量,那某线程想获取该变量的值的时候,只能去主内存中获取,其次它还可以防止指令重排序,指令重排问题会在线程安全的单例模式(懒汉)进行介绍.具体:
public class Demo3 { private static volatile boolean flag=false; public static void main(String[] args) { Thread t1=new Thread(()->{ while(!flag){ System.out.println("我是优化完之后直接读取寄存器中的变量值才打印的哦!"); } }); t1.start(); try { Thread.sleep(1);//主线程给t1留有充足的时间先跑起来 } catch (InterruptedException e) { e.printStackTrace(); } flag=true; System.out.println("我已经在主线程中修改了标志位"); } } //打印若干t1中的打印语句之后,主线程main中修改标志位之后,可以终止t1
注意:上述优化现象只会出现在频繁读的情况,如果不是频繁读,就不会出现那样的优化.
指令重排序问题
生活案例:买菜
如果是傻乎乎的按照菜单从上到下的去买菜,从路线图可以看出,不必要的路是真的没少走.
如果执行代码时,编译器认为某些个代码调整一下顺序并不会影响结果,那代码的执行顺序就会被调整,就比如可以把上面买菜的顺序调整成:黄瓜->萝卜->青菜->茄子
单线程这样的指令重排一般不会出现问题,但是多线程并发时,还这样优化,就容易出现问题
针对这样的问题,如果是针对一个变量,我们可以使用volatile修饰,如果是针对代码块,我们可以使用synchronized.
synchronized的用法
synchronized起作用的本质
修饰普通方法
修饰静态方法
修饰代码块
synchronized起作用的本质
因为我们知道java中所有类都继承了Object,所以所有类都包含了Object的部分,我们可以称这继承的部分是"对象头",使用synchronized进行对象头中的标志位的修改,就可以做到一个对象的锁一个时刻只能被一个线程所持有,其他线程此时不可抢占.这样的设置,就好像把一个对象给锁住了一样.
修饰普通方法
如前述两个线程给同一个count进行自增的案例.不再赘述.此时的所对象就是Counter对象
修饰静态方法⚡️
与普通方法类似.只不过这个方法可以类名直接调用.
修饰代码块
首先修饰代码块需要执行锁对象是谁,所以这里可以分为三类,一个是修饰普通方法的方法体这个代码块的写法,其次是修饰静态方法方法体的写法,最后可以单独写一个Object的对象,来对这个Object对象进行上锁.
class Counter{ private int count; public void increase(){ synchronized(this){ count++; } } public int getCount(){ return this.count; } }
class Counter{ private static int count; public static void increase(){ synchronized(Counter.class){//注意这里锁的是类对象哦 count++; } } public int getCount(){ return this.count; } }
class Counter{ private static int count; private static Object locker=new Object(); public static void increase(){ synchronized(locker){ count++; } } public int getCount(){ return this.count; } }
注意:java中这种随手拿一个对象就能上锁的用法,是java中一种很有特色的用法,在别的语言中,都是有专门的锁对象的.
Conclusion
java中的线程状态,以及如何区分线程安全问题 罪恶之源是抢占式执行多线程对同一个变量进行修改,多线程只读一个变量是没有线程安全问题的修改操作是非原子性的内存可见性引起的线程安全问题指令重排序引起的线程安全问题 synchronized的本质和用法
1.java中的线程状态,以及如何区分
2.线程安全问题
罪恶之源是抢占式执行
多執行緒對同一個變數進行修改,多執行緒只讀一個變數是沒有執行緒安全性問題的
修改運算是非原子性的
記憶體可見性所造成的執行緒安全性問題
指令重新排序所引起的執行緒安全性問題
3.synchronized的本質和用法
以上是Java中執行緒狀態、執行緒安全性問題和synchronized關鍵字的使用的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

Java 8引入了Stream API,提供了一種強大且表達力豐富的處理數據集合的方式。然而,使用Stream時,一個常見問題是:如何從forEach操作中中斷或返回? 傳統循環允許提前中斷或返回,但Stream的forEach方法並不直接支持這種方式。本文將解釋原因,並探討在Stream處理系統中實現提前終止的替代方法。 延伸閱讀: Java Stream API改進 理解Stream forEach forEach方法是一個終端操作,它對Stream中的每個元素執行一個操作。它的設計意圖是處

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

PHP和Python各有優勢,選擇應基於項目需求。 1.PHP適合web開發,語法簡單,執行效率高。 2.Python適用於數據科學和機器學習,語法簡潔,庫豐富。

PHP適合web開發,特別是在快速開發和處理動態內容方面表現出色,但不擅長數據科學和企業級應用。與Python相比,PHP在web開發中更具優勢,但在數據科學領域不如Python;與Java相比,PHP在企業級應用中表現較差,但在web開發中更靈活;與JavaScript相比,PHP在後端開發中更簡潔,但在前端開發中不如JavaScript。

PHP和Python各有優勢,適合不同場景。 1.PHP適用於web開發,提供內置web服務器和豐富函數庫。 2.Python適合數據科學和機器學習,語法簡潔且有強大標準庫。選擇時應根據項目需求決定。

膠囊是一種三維幾何圖形,由一個圓柱體和兩端各一個半球體組成。膠囊的體積可以通過將圓柱體的體積和兩端半球體的體積相加來計算。本教程將討論如何使用不同的方法在Java中計算給定膠囊的體積。 膠囊體積公式 膠囊體積的公式如下: 膠囊體積 = 圓柱體體積 兩個半球體體積 其中, r: 半球體的半徑。 h: 圓柱體的高度(不包括半球體)。 例子 1 輸入 半徑 = 5 單位 高度 = 10 單位 輸出 體積 = 1570.8 立方單位 解釋 使用公式計算體積: 體積 = π × r2 × h (4

PHPhassignificantlyimpactedwebdevelopmentandextendsbeyondit.1)ItpowersmajorplatformslikeWordPressandexcelsindatabaseinteractions.2)PHP'sadaptabilityallowsittoscaleforlargeapplicationsusingframeworkslikeLaravel.3)Beyondweb,PHPisusedincommand-linescrip

PHP成為許多網站首選技術棧的原因包括其易用性、強大社區支持和廣泛應用。 1)易於學習和使用,適合初學者。 2)擁有龐大的開發者社區,資源豐富。 3)廣泛應用於WordPress、Drupal等平台。 4)與Web服務器緊密集成,簡化開發部署。
