摘要:告訴當(dāng)前執(zhí)行的線程為線程池中其他具有相同優(yōu)先級的線程提供機會。不能保證會立即使當(dāng)前正在執(zhí)行的線程處于可運行狀態(tài)。當(dāng)達到超時時間時,主線程和是同樣可能的執(zhí)行者候選。下一篇并發(fā)編程線程安全性深層原因
Thread
使用Java的同學(xué)對Thread應(yīng)該不陌生了,線程的創(chuàng)建和啟動等這里就不講了,這篇主要講幾個容易被忽視的方法以及線程狀態(tài)遷移。
wait/notify/notifyAll首先我們要明白這三個方法是定義在Object類中,他們起到的作用就是允許線程就資源的鎖定狀態(tài)進行通信。這里所說的資源一般就是指的我們常說的共享對象了,也就是說針對共享對象的鎖定狀態(tài)可以通過wait/notify/notifyAll來進行通信。我們先看下如何使用的,并對相應(yīng)原理進行展開。
waitwait方法告訴調(diào)用線程放棄鎖定并進入休眠狀態(tài),直到其他某個線程進入同一個監(jiān)視器(monitor)并調(diào)用notify方法。wait方法在等待之前釋放鎖,并在wait方法返回之前重新獲取鎖。wait方法實際上和同步鎖緊密集成,補充同步機制無法直接實現(xiàn)的功能。
需要注意到wait方法在jdk源碼中是final并且是native的本地方法,我們無法去覆蓋此方法。
調(diào)用wait一般的方式如下:
synchronized(lockObject) { while(!condition) { lockObject.wait(); } // 這里進行相應(yīng)處理; }
注意這里使用while進行條件判斷而沒有使用if進行條件判斷,原因是這里有個很重要的點容易被忽視,下面來自官方的建議:
應(yīng)該在循環(huán)中檢查等待條件,原因是處于等待狀態(tài)的線程可能會收到錯誤的警報和偽喚醒,如果不在循環(huán)條件中等待,程序就會在沒有滿足結(jié)束條件的情況下退出。notify
notify方法喚醒了同一個對象上調(diào)用wait的線程。這里要注意notify并沒有放棄對資源的鎖定,他告訴等待的線程可以喚醒,但是作用在notify上synchronized同步塊完成之前,實際上是不會放棄鎖。因此,如果通知線程在同步塊內(nèi),調(diào)用notify方法后,需要在進行10s的其他操作,那么等待的線程將會再至少等待10s。
notify一般的使用方式如下:
synchronized(lockObject) { // 確定條件 lockObject.notify(); // 如果需要可以加任意代碼 }notifyAll
notifyAll會喚醒在同一個對象上調(diào)用wait方法的所有線程。在大多數(shù)情況下優(yōu)先級最高的線程將被執(zhí)行,但是也是無法完全保證會是這樣。其他的與notify相同。
使用例子下面的代碼示例實現(xiàn)了隊列空和滿時線程阻塞已經(jīng)非空非滿時的通知:
生產(chǎn)者:
class Producer implements Runnable { private final ListtaskQueue; private final int MAX_CAPACITY; public Producer(List sharedQueue, int size) { this.taskQueue = sharedQueue; this.MAX_CAPACITY = size; } @Override public void run() { int counter = 0; while (true) { try { produce(counter++); } catch (InterruptedException ex) { ex.printStackTrace(); } } } private void produce(int i) throws InterruptedException { synchronized (taskQueue) { while (taskQueue.size() == MAX_CAPACITY) { System.out.println("隊列已滿,線程" + Thread.currentThread().getName() + "進入等待,隊列長度:" + taskQueue.size()); taskQueue.wait(); } Thread.sleep(1000); taskQueue.add(i); System.out.println("生產(chǎn):" + i); taskQueue.notifyAll(); } } }
消費者:
class Consumer implements Runnable { private final ListtaskQueue; public Consumer(List sharedQueue) { this.taskQueue = sharedQueue; } @Override public void run() { while (true) { try { consume(); } catch (InterruptedException ex) { ex.printStackTrace(); } } } private void consume() throws InterruptedException { synchronized (taskQueue) { while (taskQueue.isEmpty()) { System.out.println("隊列已空,線程" + Thread.currentThread().getName() + "進入等待,隊列長度:" + taskQueue.size()); taskQueue.wait(); } Thread.sleep(1000); int i = (Integer) taskQueue.remove(0); System.out.println("消費:" + i); taskQueue.notifyAll(); } } }
測試代碼:
public class ProducerConsumerExampleWithWaitAndNotify { public static void main(String[] args) { ListtaskQueue = new ArrayList<>(); int MAX_CAPACITY = 5; Thread tProducer = new Thread(new Producer(taskQueue, MAX_CAPACITY), "Producer"); Thread tConsumer = new Thread(new Consumer(taskQueue), "Consumer"); tProducer.start(); tConsumer.start(); } }
部分輸出如下:
生產(chǎn):0 生產(chǎn):1 生產(chǎn):2 生產(chǎn):3 生產(chǎn):4 隊列已滿,線程Producer進入等待,隊列長度:5 消費:0 消費:1 消費:2 消費:3 消費:4 隊列已空,線程Consumer進入等待,隊列長度:0yield/join yield
從字面意思理解yield可以是謙讓、放棄、屈服、投降的意思。一個要“謙讓”的線程其實是在告訴虛擬機他愿意讓其他線程安排到他的前面,這表明他沒有說明重要的事情要做了。注意了這只是個提示,并不能保證能起到任何效果。
yield在Thread.java中定義如下:
/** * 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-utilise a CPU. Its use * should be combined with detailed profiling and benchmarking to * ensure that it actually has the desired effect. * *
It is rarely appropriate to use this method. It may be useful * for debugging or testing purposes, where it may help to reproduce * bugs due to race conditions. It may also be useful when designing * concurrency control constructs such as the ones in the * {@link java.util.concurrent.locks} package. */ public static native void yield();
從這里面我們總結(jié)出一些重點(有關(guān)線程狀態(tài)后面會講到):
yield方法是一個靜態(tài)的并且是native的方法。
yield告訴當(dāng)前執(zhí)行的線程為線程池中其他具有相同優(yōu)先級的線程提供機會。
不能保證yield會立即使當(dāng)前正在執(zhí)行的線程處于可運行狀態(tài)。
他只能使得線程從運行狀態(tài)變成可運行狀態(tài),而無法做其他狀態(tài)改變。
yield使用例子:
public class YieldExample { public static void main(String[] args) { Thread producer = new Producer(); Thread consumer = new Consumer(); producer.setPriority(Thread.MIN_PRIORITY); // 最低優(yōu)先級 consumer.setPriority(Thread.MAX_PRIORITY); // 最高優(yōu)先級 producer.start(); consumer.start(); } } class Producer extends Thread { public void run() { for (int i = 0; i < 5; i++) { System.out.println("生產(chǎn)者 : 生產(chǎn) " + i); Thread.yield(); } } } class Consumer extends Thread { public void run() { for (int i = 0; i < 5; i++) { System.out.println("消費者 : 消費 " + i); Thread.yield(); } } }
當(dāng)注釋兩個“Thread.yield();”時輸出:
消費者 : 消費 0 消費者 : 消費 1 消費者 : 消費 2 消費者 : 消費 3 消費者 : 消費 4 生產(chǎn)者 : 生產(chǎn) 0 生產(chǎn)者 : 生產(chǎn) 1 生產(chǎn)者 : 生產(chǎn) 2 生產(chǎn)者 : 生產(chǎn) 3 生產(chǎn)者 : 生產(chǎn) 4
當(dāng)不注釋兩個“Thread.yield();”時輸出:
生產(chǎn)者 : 生產(chǎn) 0 消費者 : 消費 0 生產(chǎn)者 : 生產(chǎn) 1 消費者 : 消費 1 生產(chǎn)者 : 生產(chǎn) 2 消費者 : 消費 2 生產(chǎn)者 : 生產(chǎn) 3 消費者 : 消費 3 生產(chǎn)者 : 生產(chǎn) 4 消費者 : 消費 4join
join方法用于將線程當(dāng)前執(zhí)行點連接到另一個線程的執(zhí)行結(jié)束,這樣這個線程就不會開始運行直到另一個線程結(jié)束。在Thread實例上調(diào)用join,則當(dāng)前運行的線程將會阻塞,直到這個Thread實例完成執(zhí)行。
簡要摘抄Thread.java源碼中join的定義:
// Waits for this thread to die. public final void join() throws InterruptedException
join還有可以傳入時間參數(shù)的重載方法,這個可以時join的效果在特定時間后無效。當(dāng)達到超時時間時,主線程和taskThread是同樣可能的執(zhí)行者候選。但是join和sleep一樣,依賴于OS進行計時,不應(yīng)該假定剛好等待指定的時間。
join和sleep一樣也通過InterruptedException來響應(yīng)中斷。
join使用示例:
public class JoinExample { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(new Runnable() { public void run() { System.out.println("第一個任務(wù)啟動"); System.out.println("睡眠2s"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("第一個任務(wù)完成"); } }); Thread t1 = new Thread(new Runnable() { public void run() { System.out.println("第二個任務(wù)完成"); } }); t.start(); t.join(); t1.start(); } }
輸出結(jié)果:
第一個任務(wù)啟動 睡眠2s 第一個任務(wù)完成 第二個任務(wù)完成join原理分析
join在Thread.java中有三個重載方法:
public final void join() throws InterruptedException public final synchronized void join(long millis) throws InterruptedException public final synchronized void join(long millis, int nanos) throws InterruptedException
查看源碼可以得知最終的實現(xiàn)核心部分都在join(long millis)中,我們來分析下這個方法源碼:
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; } } }
首先可以看到這個方法是使用synchronized修飾的同步方法,從這個方法的源碼可以看出join的核心就是使用wait來實現(xiàn)的,而外部條件就是isAlive(),可以斷定,在非isAlive()時會進行notify。
線程狀態(tài)在Thread.java的源代碼中就體現(xiàn)出了六種狀態(tài):
/** * A thread state. A thread can be in one of the following states: *
* A thread can be in only one state at a given point in time. * These states are virtual machine states which do not reflect * any operating system thread states. * * @since 1.5 * @see #getState */ public enum State { /** * Thread state for a thread which has not yet started. */ NEW, /** * Thread state for a runnable thread. A thread in the runnable * state is executing in the Java virtual machine but it may * be waiting for other resources from the operating system * such as processor. */ RUNNABLE, /** * Thread state for a thread blocked waiting for a monitor lock. * A thread in the blocked state is waiting for a monitor lock * to enter a synchronized block/method or * reenter a synchronized block/method after calling * {@link Object#wait() Object.wait}. */ BLOCKED, /** * Thread state for a waiting thread. * A thread is in the waiting state due to calling one of the * following methods: *
A thread in the waiting state is waiting for another thread to * perform a particular action. * * For example, a thread that has called Object.wait() * on an object is waiting for another thread to call * Object.notify() or Object.notifyAll() on * that object. A thread that has called Thread.join() * is waiting for a specified thread to terminate. */ WAITING, /** * Thread state for a waiting thread with a specified waiting time. * A thread is in the timed waiting state due to calling one of * the following methods with a specified positive waiting time: *
一般我們用如下圖來表示狀態(tài)遷移,注意相關(guān)方法。(注意:其中RUNNING和READY是無法直接獲取的狀態(tài)。)
下一篇:Java并發(fā)編程——線程安全性深層原因
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/72737.html
摘要:線程安全性深層原因這里我們將會從計算機硬件和編輯器等方面來詳細(xì)了解線程安全產(chǎn)生的深層原因。類似這種不影響單線程語義的亂序執(zhí)行我們稱為指令重排。通過線程安全性深層原因我們能更好的理解這三大性質(zhì)的根本性原因。上一篇并發(fā)編程線程基礎(chǔ)查漏補缺 線程安全性深層原因 這里我們將會從計算機硬件和編輯器等方面來詳細(xì)了解線程安全產(chǎn)生的深層原因。 緩存一致性問題 CPU內(nèi)存架構(gòu) 隨著CPU的發(fā)展,而因為C...
摘要:作為技術(shù)書籍或者視頻,講解一門語言的時候都是從最底層開始講解,底層的基礎(chǔ)有哪些呢首先是整個,讓我們對這門語言先混個臉熟,知道程序的基本結(jié)構(gòu),順帶著還會說一下注釋是什么樣子。 2018年新年剛過,就迷茫了,Java學(xué)不下去了,不知道從哪里學(xué)了。 那么多細(xì)節(jié)的東西,我根本記不住,看完就忘。 剛開始學(xué)習(xí)的時候熱情萬丈,持續(xù)不了幾天就慢慢退去。 作為技術(shù)書籍或者視頻,講解一門語言的時候都是...
摘要:因為在頁面加載完成后,引擎維護著兩個隊列,一個是按頁面順序加載的執(zhí)行隊列,還有一個空閑隊列,使用定時函數(shù)就是將回調(diào)函數(shù)加入到空閑隊列中,故和其他定時器是并發(fā)執(zhí)行的。 1.window.onload和$(document).ready()的區(qū)別: ①執(zhí)行時間:window.onload會在所有元素,包括圖片,引用文件加載完成之后執(zhí)行,而$(document).ready()則會在HTML...
摘要:作為面試官,我是如何甄別應(yīng)聘者的包裝程度語言和等其他語言的對比分析和主從復(fù)制的原理詳解和持久化的原理是什么面試中經(jīng)常被問到的持久化與恢復(fù)實現(xiàn)故障恢復(fù)自動化詳解哨兵技術(shù)查漏補缺最易錯過的技術(shù)要點大掃盲意外宕機不難解決,但你真的懂?dāng)?shù)據(jù)恢復(fù)嗎每秒 作為面試官,我是如何甄別應(yīng)聘者的包裝程度Go語言和Java、python等其他語言的對比分析 Redis和MySQL Redis:主從復(fù)制的原理詳...
閱讀 1815·2019-08-30 13:54
閱讀 2728·2019-08-29 17:27
閱讀 1114·2019-08-29 17:23
閱讀 3354·2019-08-29 15:20
閱讀 1229·2019-08-29 11:28
閱讀 1570·2019-08-26 10:39
閱讀 1318·2019-08-26 10:29
閱讀 642·2019-08-26 10:13