Java wait forever_彻底搞清楚Java并发 (一) 基础
多線程編程是為了讓程序運行得更快,但是不是說,線程創建地越多越好,線程切換的時候上下文切換,以及受限于硬件和軟件資源的限制問題
上下文切換
單核CPU同樣支持多線程編程,CPU通過給每個線程分配CPU時間片來實現這個機制,時間片是CPU分配給各個線程的時間,這個時間片非常短,所以就不得不通過切換線程來執行(時間片一般是幾十毫秒)
當前任務執行一個時間片后,會切換到下一個任務,但是,在切換前會保存上一個任務的狀態,這樣的話下次這條線程獲取到時間片之后就可以恢復這個任務的狀態
協程
協程說通俗一點就是由線程調度的線程,操作系統創建一個進程,進程再創建若干個線程并行,線程的切換由操作系統負責調度,Java語言等線程其實與操作系統線程是1:1的關系,每個線程都有自己的Stack,Java在64位操作系統默默人stack大小為1024kb,所以一個進程也是不能夠開啟上萬個線程的
基于J2EE項目都是基于每個請求占用一個線程去完成完整的業務邏輯,(包括事務)。所以系統的吞吐能力取決于每個線程的操作耗時。如果遇到很耗時的I/O行為,則整個系統的吞吐立刻下降,比如JDBC是同步阻塞的,這也是為什么很多人都說數據庫是瓶頸的原因。這里的耗時其實是讓CPU一直在等待I/O返回,說白了線程根本沒有利用CPU去做運算,而是處于空轉狀態。
Java的JDK里有封裝很好的ThreadPool,可以用來管理大量的線程生命周期,但是本質上還是不能很好的解決線程數量的問題,以及線程空轉占用CPU資源的問題。
先階段行業里的比較流行的解決方案之一就是單線程加上異步回調。其代表派是node.js以及Java里的新秀Vert.x。他們的核心思想是一樣的,遇到需要進行I/O操作的地方,就直接讓出CPU資源,然后注冊一個回調函數,其他邏輯則繼續往下走,I/O結束后帶著結果向事件隊列里插入執行結果,然后由事件調度器調度回調函數,傳入結果。
協程的本質上其實還是和上面的方法一樣,只不過他的核心點在于由他進行調度,遇到阻塞操作,立刻yield掉,并且記錄當前棧上的數據,阻塞完后立刻再找一個線程恢復棧并把阻塞的結果放到這個線程上去跑,這樣看上去好像跟寫同步代碼沒有任何差別,這整個流程可以稱為coroutine,而跑在由coroutine負責調度的線程稱為Fiber。比如Golang里的 go關鍵字其實就是負責開啟一個Fiber,讓func邏輯跑在上面。而這一切都是發生的用戶態上,沒有發生在內核態上,也就是說沒有切換上下文的開銷。
Java線程調度
JVM必須維護一個有優先權,基于優先級的調度模式,優先級的值很重要,因為Java虛擬機和下層的操作系統之間的約定是操作系統必須選擇有最高優先權的Java線程運行,所以我們說Java實現了一個基于優先權的調度程序該調度程序使用一種有優先權的方式實現,這意味著當一個有更高優先權的線程到來時,無論低優先級的線程是否在運行,都會中斷(搶占)它(JVM會這么做)。這個約定對于操作系統來說并不總是這樣,這意味著操作系統有時可能會選擇運行一個更低優先級的線程。
yield()方法
理論上,yield意味著放手,放棄,投降。一個調用yield()方法的線程告訴虛擬機它樂意讓其他線程占用自己的位置。這表明該線程沒有在做一些緊急的事情。注意,這僅是一個暗示,并不能保證不會產生任何影響。
/**
* A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore
* this hint. Yield is a heuristic attempt to improve relative progression between threads that would otherwise over-utilize a CPU.
* Its use should be combined with detailed profiling and benchmarking to ensure that it actually has the desired effect.
*/
public static native void yield();
Yield是一個靜態的本地(native)方法
Yield告訴當前正在執行的線程把運行機會交給線程池中擁有相同優先級的線程。
Yield不能保證使得當前正在運行的線程迅速轉換到可運行的狀態
它僅能使一個線程從運行狀態轉到可運行狀態,而不是等待或阻塞狀態
join()方法
如果一個線程A執行了thread.join()方法目的是,當前線程A等待thread線程終止之后才從thread.join()返回,
/**
* Waits for this thread to die.
*
*
An invocation of this method behaves in exactly the same
* way as the invocation
*
*
* {@linkplain #join(long) join}{@code (0)}
*
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* interrupted status of the current thread is
* cleared when this exception is thrown.
*/
public final void join() throws InterruptedException {
join(0);
}
/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
*
*
This implementation uses a loop of {@code this.wait} calls
* conditioned on {@code this.isAlive}. As a thread terminates the
* {@code this.notifyAll} method is invoked. It is recommended that
* applications not use {@code wait}, {@code notify}, or
* {@code notifyAll} on {@code Thread} instances.
*
* @param millis
* the time to wait in milliseconds
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* interrupted status of the current thread is
* cleared when this exception is thrown.
*/
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
如何減少上下文切換
無鎖并發編程,多線程競爭鎖的時候,會引起上下文切換,如將數據的ID按照Hash算法取模分段,不同線程處理不同段的數據
CAS算法,Java的Atomic包下使用的同步類都是使用CAS和Volatile實現的無鎖
線程數量的控制
協程:單線程內實現多任務的調度,在單線程中維持多個任務間的切換
如何避免死鎖
避免一個線程同時獲得多個鎖
避免一個線程在鎖內同時占用多個資源,盡量保證每個鎖只占用一個資源
嘗試使用定時鎖,tryLock(time)代替內部鎖的機制
對于數據庫來說,加鎖和解鎖必須在同一條連接中,否則將會出現問題
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的Java wait forever_彻底搞清楚Java并发 (一) 基础的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java并发进程共享变量_JAVA并发编
- 下一篇: Java双大括号_什么是Java中的双B