Java wait forever_彻底搞清楚Java并发 (一) 基础
多線程編程是為了讓程序運(yùn)行得更快,但是不是說,線程創(chuàng)建地越多越好,線程切換的時(shí)候上下文切換,以及受限于硬件和軟件資源的限制問題
上下文切換
單核CPU同樣支持多線程編程,CPU通過給每個(gè)線程分配CPU時(shí)間片來實(shí)現(xiàn)這個(gè)機(jī)制,時(shí)間片是CPU分配給各個(gè)線程的時(shí)間,這個(gè)時(shí)間片非常短,所以就不得不通過切換線程來執(zhí)行(時(shí)間片一般是幾十毫秒)
當(dāng)前任務(wù)執(zhí)行一個(gè)時(shí)間片后,會(huì)切換到下一個(gè)任務(wù),但是,在切換前會(huì)保存上一個(gè)任務(wù)的狀態(tài),這樣的話下次這條線程獲取到時(shí)間片之后就可以恢復(fù)這個(gè)任務(wù)的狀態(tài)
協(xié)程
協(xié)程說通俗一點(diǎn)就是由線程調(diào)度的線程,操作系統(tǒng)創(chuàng)建一個(gè)進(jìn)程,進(jìn)程再創(chuàng)建若干個(gè)線程并行,線程的切換由操作系統(tǒng)負(fù)責(zé)調(diào)度,Java語言等線程其實(shí)與操作系統(tǒng)線程是1:1的關(guān)系,每個(gè)線程都有自己的Stack,Java在64位操作系統(tǒng)默默人stack大小為1024kb,所以一個(gè)進(jìn)程也是不能夠開啟上萬個(gè)線程的
基于J2EE項(xiàng)目都是基于每個(gè)請(qǐng)求占用一個(gè)線程去完成完整的業(yè)務(wù)邏輯,(包括事務(wù))。所以系統(tǒng)的吞吐能力取決于每個(gè)線程的操作耗時(shí)。如果遇到很耗時(shí)的I/O行為,則整個(gè)系統(tǒng)的吞吐立刻下降,比如JDBC是同步阻塞的,這也是為什么很多人都說數(shù)據(jù)庫是瓶頸的原因。這里的耗時(shí)其實(shí)是讓CPU一直在等待I/O返回,說白了線程根本沒有利用CPU去做運(yùn)算,而是處于空轉(zhuǎn)狀態(tài)。
Java的JDK里有封裝很好的ThreadPool,可以用來管理大量的線程生命周期,但是本質(zhì)上還是不能很好的解決線程數(shù)量的問題,以及線程空轉(zhuǎn)占用CPU資源的問題。
先階段行業(yè)里的比較流行的解決方案之一就是單線程加上異步回調(diào)。其代表派是node.js以及Java里的新秀Vert.x。他們的核心思想是一樣的,遇到需要進(jìn)行I/O操作的地方,就直接讓出CPU資源,然后注冊(cè)一個(gè)回調(diào)函數(shù),其他邏輯則繼續(xù)往下走,I/O結(jié)束后帶著結(jié)果向事件隊(duì)列里插入執(zhí)行結(jié)果,然后由事件調(diào)度器調(diào)度回調(diào)函數(shù),傳入結(jié)果。
協(xié)程的本質(zhì)上其實(shí)還是和上面的方法一樣,只不過他的核心點(diǎn)在于由他進(jìn)行調(diào)度,遇到阻塞操作,立刻yield掉,并且記錄當(dāng)前棧上的數(shù)據(jù),阻塞完后立刻再找一個(gè)線程恢復(fù)棧并把阻塞的結(jié)果放到這個(gè)線程上去跑,這樣看上去好像跟寫同步代碼沒有任何差別,這整個(gè)流程可以稱為coroutine,而跑在由coroutine負(fù)責(zé)調(diào)度的線程稱為Fiber。比如Golang里的 go關(guān)鍵字其實(shí)就是負(fù)責(zé)開啟一個(gè)Fiber,讓func邏輯跑在上面。而這一切都是發(fā)生的用戶態(tài)上,沒有發(fā)生在內(nèi)核態(tài)上,也就是說沒有切換上下文的開銷。
Java線程調(diào)度
JVM必須維護(hù)一個(gè)有優(yōu)先權(quán),基于優(yōu)先級(jí)的調(diào)度模式,優(yōu)先級(jí)的值很重要,因?yàn)镴ava虛擬機(jī)和下層的操作系統(tǒng)之間的約定是操作系統(tǒng)必須選擇有最高優(yōu)先權(quán)的Java線程運(yùn)行,所以我們說Java實(shí)現(xiàn)了一個(gè)基于優(yōu)先權(quán)的調(diào)度程序該調(diào)度程序使用一種有優(yōu)先權(quán)的方式實(shí)現(xiàn),這意味著當(dāng)一個(gè)有更高優(yōu)先權(quán)的線程到來時(shí),無論低優(yōu)先級(jí)的線程是否在運(yùn)行,都會(huì)中斷(搶占)它(JVM會(huì)這么做)。這個(gè)約定對(duì)于操作系統(tǒng)來說并不總是這樣,這意味著操作系統(tǒng)有時(shí)可能會(huì)選擇運(yùn)行一個(gè)更低優(yōu)先級(jí)的線程。
yield()方法
理論上,yield意味著放手,放棄,投降。一個(gè)調(diào)用yield()方法的線程告訴虛擬機(jī)它樂意讓其他線程占用自己的位置。這表明該線程沒有在做一些緊急的事情。注意,這僅是一個(gè)暗示,并不能保證不會(huì)產(chǎn)生任何影響。
/**
* 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是一個(gè)靜態(tài)的本地(native)方法
Yield告訴當(dāng)前正在執(zhí)行的線程把運(yùn)行機(jī)會(huì)交給線程池中擁有相同優(yōu)先級(jí)的線程。
Yield不能保證使得當(dāng)前正在運(yùn)行的線程迅速轉(zhuǎn)換到可運(yùn)行的狀態(tài)
它僅能使一個(gè)線程從運(yùn)行狀態(tài)轉(zhuǎn)到可運(yùn)行狀態(tài),而不是等待或阻塞狀態(tài)
join()方法
如果一個(gè)線程A執(zhí)行了thread.join()方法目的是,當(dāng)前線程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;
}
}
}
如何減少上下文切換
無鎖并發(fā)編程,多線程競(jìng)爭(zhēng)鎖的時(shí)候,會(huì)引起上下文切換,如將數(shù)據(jù)的ID按照Hash算法取模分段,不同線程處理不同段的數(shù)據(jù)
CAS算法,Java的Atomic包下使用的同步類都是使用CAS和Volatile實(shí)現(xiàn)的無鎖
線程數(shù)量的控制
協(xié)程:單線程內(nèi)實(shí)現(xiàn)多任務(wù)的調(diào)度,在單線程中維持多個(gè)任務(wù)間的切換
如何避免死鎖
避免一個(gè)線程同時(shí)獲得多個(gè)鎖
避免一個(gè)線程在鎖內(nèi)同時(shí)占用多個(gè)資源,盡量保證每個(gè)鎖只占用一個(gè)資源
嘗試使用定時(shí)鎖,tryLock(time)代替內(nèi)部鎖的機(jī)制
對(duì)于數(shù)據(jù)庫來說,加鎖和解鎖必須在同一條連接中,否則將會(huì)出現(xiàn)問題
與50位技術(shù)專家面對(duì)面20年技術(shù)見證,附贈(zèng)技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的Java wait forever_彻底搞清楚Java并发 (一) 基础的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java并发进程共享变量_JAVA并发编
- 下一篇: JAVA数据库访问设置的实验_实验十一