java無狀態
A. 深入理解java線程
深入理解Java線程進程和線程進程
管道以及有名管道
信號
信號量
消息隊列
共享內存,比如實現分布式鎖
套接字
進程是操作系統資源分配的最小單位
問題: 進程之間是如何通信的,有哪些方式
線程
線程同步: 線程之間存在一種關系,一個線程需要等待另外一個線程的消息之後才能進行,否則就需要等待
線程互斥: 對於共享資源只能線程獨享,想要獲取需要等待另外一個線程釋放資源
volatile保證線程之間共享變數的可見性
管道輸入輸出流: PipedWriter、PIpedReader
join: 基於等待喚醒機制
線程是操作系統線程調度和執行的最小單位,而線程歸屬於進程
問題 Java線程之間如何通信的,有哪些方式
問題: 線程的同步和互斥
問題: 線程和進程之間的區別
線程更輕量級,線程的上下文切換成本比進程上下文切換成本低
進程間的通信比較復雜,線程之間的通信比較簡單比如共享進程內的內存
進程是操作系統資源分配的最小單位,線程是操作系統線程調度和執行的最小單位,而線程歸屬於進程
問題: 四種線程同步互斥的控制方法
臨界區: 通過對多線程的串列化來訪問公共資源或一段代碼,速度快,適合控制數據訪問(在一段時間內只允許一個線程訪問的資源就稱為臨界資源)
互斥量: 為協調共同對一個共享資源的單獨訪問而設計的
信號量: 為控制一個具有有限數量用戶資源而設計
事件: 用來通知線程有一些事件已發生,從而啟動後繼任務的開始
上下文切換問題: 什麼是上下文切換
上下文切換是指CPU從一個進程或線程切換到另外一個線程或者進程,上下文切換會保存上一次的狀態,以便於下一次繼續執行
上下文切換只發生在內核態
上下文切換耗費時間成本比較大,盡量避免
問題: 上下文切換的步驟
暫停當前線程的處理,將當前線程的上下文保存下來,執行下一個線程的處理直到時間片用完暫停,再通過之前保存的上下文去繼續執行之前線程的處理
問題: 造成CPU上下文切換的方式
進程和線程的切換
系統調用
中斷機制
內核模式和用戶模式問題: 什麼是內核模式和用戶模式
在用戶態,執行代碼不能直接訪問底層硬體,需要通過系統調用
在內核態,執行代碼可以完全不受限制的訪問底層硬體
內核模式(內核態)
用戶模式(用戶態)
問題: CAS操作是否涉及到用戶態到內核態的切換
CAS不會涉及到用戶態到內核態的切換,CAS在多核處理器下相當於在代碼里插入了lock cmpxchgl指令來保證原子性,而且執行指令比上下文切換開銷小,所以CAS比互斥鎖性能更高
操作系統層面線程的生命周期操作系統層面線程的生命周期
線程一開始被創建時進入初始狀態,然後可以被分配給CPU時處於就緒狀態,當CPU空閑的時會從就緒狀態的線程中挑選一個線程去執行進入運行狀態,當運行狀態下的線程調用阻塞API時會進入阻塞狀態等待被喚醒繼續運行,當線程執行完或被異常停止處於終止狀態
初始狀態: 線程已經被創建,但是還不允許CPU執行
就緒狀態: 線程可以分配給CPU執行
運行狀態: 當CPU空閑的時候,會將它分配到一個就緒狀態的線程去使用
休眠狀態: 運行狀態的線程調用阻塞的API時會進入阻塞狀態等待被喚醒繼續運行
終止狀態: 線程執行結束或遇到異常
小結
Java層面線程的生命周期Java層面線程的生命周期
NEW(初始化狀態)
RUNNABLE(就緒狀態 + 運行狀態 = 可運行狀態)
BLOCKED(阻塞狀態): 只有synchronized使用
TIMED_WAITING(有時限等待狀態): 調用wait()方法時指定等待的時長就會處於此狀態
TERMINATED(終止狀態)
問題: 概括的解釋下線程的幾種狀態
阻塞狀態就是指線程因為某種原因放棄了cpu使用權,也就是讓出了cpu時間片,暫時停止運行,直到線程進入可運行狀態,才有機會獲得cpu時間片轉到運行狀態
阻塞的情況分三種
死亡(dead): 線程run、main方法執行結束、異常退出run,就代表該線程生命周期結束,死亡的線程不可再次復生
等待阻塞: 運行的線程執行wait方法,JVM會把線程放入等待隊列中
同步阻塞: 運行線程在獲取對象的同步鎖的時候,如果鎖沒被釋放,JVM會把該線程放入鎖池中
其他阻塞: 運行的線程執行sleep、join、發送IO請求時,JVM會把線程變為阻塞狀態,當sleep超時、join等待線程終止或超時、IO處理完畢時,線程重新轉成可運行狀態
可運行狀態的線程獲得了cpu時間片,執行程序代碼
線程對象創建後,其他線程比如main線程調用了該對象的start方法,該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取CPU的使用權
新創建一個線程對象
新建(new)
可運行(runnable)
運行(running)
阻塞(block)
Java線程Java線程概述Java線程屬於內核級線程,是依賴於內核的也就是無論是用戶進程中的線程還是系統進程中的線程,它們的創建、撤銷、切換都是需要切換到內核態去執行
問題: ?為什麼說創建Java線程的方式本質只有一種
繼承Thread類實現run方法
實現Runable介面,實現run方法
實現Callable介面,實現call方法
通過線程池去創建線程: 推薦使用
雖然說創建線程的方式有以下幾種
但是本質只有一種,都是通過new Thread創建線程,調用Thread.start啟動線程,最終都會去調用Threead.run
問題: Java線程和Go的協程有什麼區別
協程是基於線程之上但是又更加輕量級的存在,協程存在於用戶態,不被操作系統內核管理
什麼是協程
如果線程不用切換到內核態,開銷非常小,就可以創建多個用戶級別來執行任務,這樣並發量特別高,所以Go天生就是和做這種大量並發的場景
問題: Java線程執行為什麼不能直接調用run方法,而要調用start方法
因為run方法並不是真正的線程,只是普通對象的方法,而start方法會通過操作系統去創建線程需要切換到內核態,Java線程的創建和銷毀是個比較重的操作,因為涉及到內核態切換,所以我們一般不會每一個任務分配一個線程而是選擇線程復用的方式比如使用線程池
Thread的常用方法sleep
調用sleep會讓當前線程從RUNNING進入TIMED_WAITING,不會釋放對象鎖
其他線程可以通過interrupt方法打斷正在睡眠的線程,sleep方法會拋出終端異常並且清除中斷標志
睡眠結束後的線程未必立刻得到執行
sleep傳入參數為0時和yield相同
yield
yield會釋放CPU資源,讓當前線程從RUNNING進入RUNNABLE狀態,讓優先順序更高的線程獲得執行機會,不會釋放對象鎖
假設當前進程只有main線程,當調用yield之後,main線程會繼續運行,因為沒有比它優先順序更高的線程
具體的實現依賴於操作系統的任務調度
join
可以理解為線程合並,當在一個線程調用另外一個線程的join時,當前線程阻塞等待被調用join的線程執行完畢才能繼續執行,所以join的好處就是能夠保證線程的執行順序,但如果調用線程的join方法其實已經失去了並行的意義,雖然存在多個線程,但本質上是串列的,最後join底層也是採用等待喚醒機制
等待調用join方法的線程結束之後,程序再繼續執行,一般用於等待非同步線程執行完結果之後才能繼續運行的場景
注意
stop
stop方法會釋放對象鎖,可能會造成數據不一致,因為stop方法太暴力,會強行把執行到一半的線程終止
Java線程的實現原理線程創建和啟動流程
使用new Thread()創建一個線程,然後調用start()方法進行java層面線程啟動
使用本地方法start0(),去調用JVM中的JVM_StartThread()方法創建和啟動
調用new JavaThread(&thread_entry,sz)進行線程的創建,並根據不同的操作系統平台調用對應os::create_thread()方法進行線程創建
新創建的線程狀態是initialized,調用了sync->wait()的方法進行等待,等到被喚醒才繼續執行thread->run()
調用Thread.start(native_thread)方法進行線程啟動,此時將線程狀態設置為RUNNABLE,接著調用os::start_thread(thread),根據不同的操作系統選擇不同的線程啟動方式
線程啟動之後狀態設置為RUNNABLE,並且喚醒第四步中等待的線程,接著執行thread->run()方法
JavaThread::run()方法會回調第一步的new Thread()中復寫的run()方法
Java線程的調度機制協同式線程調度: 線程執行時間由線程本身控制,但缺點是線程執行時間不可控制,如果一個線程有問題,可能一直阻塞在那
搶占式線程調度: 無法控制CPU時間片在哪停止,且線程的切換不由線程本身決定,Java默認就是搶占度調度
注意
輪循調度優點是簡潔性,它無序記錄當前所有連接的狀態,所以它是一種無狀態調度
搶占式調度實現相對復雜
Java線程的中斷機制Java沒有提供一種安全、直接的方法來停止某個線程,而是提供了中斷機制
中斷機制是一種協作機制,也就是說通過中斷並不能直接終止另一個線程,而需要被中斷的線程自己處理
被中斷的線程擁有完全的自主權,它既可以選擇立即停止,也可以選擇一段時間後停止,也可以選擇壓根不停止
API的使用
interrupt(): 將線程的中斷標志位設置為true,不會停止線程
isInterrupted(): 判斷當前線程的中斷標志位是否為true,不會清除中斷標志位
Thread.interrupted():判斷當前線程的中斷標志位是否為true,並清除中斷標志位,重置為fasle
問題: 如何優雅的終止線程
stop會釋放鎖,強制終止線程,不推薦使用
可以通過while配合isInterrupted方法以及對應的結束標記來使用,注意如果代碼塊中有調用清除中斷標記為的API時,如果使用了sleep、wait記得手動添加標記位
等待喚醒機制等待喚醒機制可以基於wait和notify方法來實現,在一個線程內調用該線程鎖對象的wait方法,線程將進入等待隊列進行等待直到被喚醒
Monitor機制去提供,只作用於synchronized同步塊,而且無法喚醒指定線程,而unpark可以指定線程,notify不可提前調用
notify()是隨機性的,只隨機喚醒一個 wait 線程
notifyAll()會喚醒所有wait線程
一般使用這種,可以喚醒指定線程,unpark提前去掉也是可以的
park/unpark
wait/notify/notifyAll
協程協程是一種基於線程之上,但又比線程更加輕量級的存在,協程不是被操作系統內核所管理,而完全是由程序所控制(也就是在用戶態執行),具有對內核來說不可見的特性。這樣帶來的好處就是性能得到了很大的提升
問題: 協程的特點在於是一個線程執行,那和多線程比,協程有何優勢
線程的切換由操作系統調度,協程由用戶自己進行調度,因此減少了上下文切換,提高了效率
線程是默認stack大小是1M,而協程更輕量,接近1k.因此可以在相同的內存中開啟更多的協程
不需要多線程的鎖機制,因為只有一個線程,也不存在同時寫變數沖突,在協程中控制共享資源不加鎖,只需要判斷狀態就好了,所以執行效率比多線程高很多
問題: Java中是否存在協程
kilim ?quasar框架
注意
協程適用於被阻塞的,且需要大量並發的場景(網路io)
不適合大量計算的場景
原文:https://juejin.cn/post/7100994816468582407
B. 如何平滑部署JavaWeb項目,讓客戶端不掉線
1. Java Web應用需要改造成無狀態的。
2. 用戶Session數據一定不要保存在Java應用中,重啟或者crash都會導致會話失效。
3. 常見的用戶Session存儲使用Memcached集群,Redis有點大材小用了。
4. 反向代理是用戶請求的第一站,推薦nginx。
5. 應用部署的第一步就是關流量,保證沒有請求到需要重新部署的服務實例上。