成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

多線程編程:wait, notify, join, yield都有啥用?

lovXin / 2992人閱讀

摘要:通知任一一個(gè)進(jìn)入等待狀態(tài)的線程,通知所有讓調(diào)用線程阻塞在這個(gè)方法上,直到的線程完全執(zhí)行完畢,調(diào)用線程才會(huì)繼續(xù)執(zhí)行。通知調(diào)度器,主動(dòng)讓出對的占用。

多線程在開發(fā)知識(shí)中是一個(gè)很重要的部分,然而實(shí)際生產(chǎn)中卻很少遇到真正需要自己去處理多線程編程里的那些復(fù)雜細(xì)節(jié)和問題,因?yàn)楹芏鄷r(shí)候,都有一套“架構(gòu)”或者一些“框架”幫大部分業(yè)務(wù)程序員隱藏了多線程的細(xì)節(jié),大多時(shí)候只需要簡單的實(shí)現(xiàn)各種業(yè)務(wù)邏輯即可。

今天來理一理wait, notify, join, yield這四個(gè)方法的作用。

這4個(gè)方法,其中wait, notify都是Object的方法,join是Thread的實(shí)例方法,yield是Thread的靜態(tài)方法。

wait, notify在之前的文章:xxxx中我已經(jīng)提到過,wait將線程轉(zhuǎn)換為Waiting狀態(tài),notify喚醒一個(gè)在Waiting狀態(tài)的線程。

咱們一個(gè)個(gè)來說。

Object.wait

文檔上是這樣描述的:

Causes the current thread to wait until either another thread invokes the Object#notify() method or the Object#notifyAll() method for this object, or a specified amount of time has elapsed.

它是說:導(dǎo)致當(dāng)前線程進(jìn)入waiting,直到另一個(gè)線程調(diào)用notify或者notifyAll方法來喚醒它,或者是指定了等待時(shí)間。

也就是用wait的有參的重載方法wait(long),可以讓線程至多等待一定的時(shí)間,這個(gè)時(shí)間過了之后,線程就自行恢復(fù)runnable狀態(tài)了。

正確的使用方法是在synchronized里面使用,并且使用一個(gè)循環(huán)將它包起來。

synchronized (lock) {
    while (!condition) {
        lock.wait() // 進(jìn)入 waiting 狀態(tài), 這行代碼之后的代碼將不會(huì)被執(zhí)行
    }
}

為什么要使用一個(gè)循環(huán)呢?因?yàn)橥ǔG闆r下,按照邏輯的要求是達(dá)到某種條件之前,我這個(gè)線程就不工作了,當(dāng)條件滿足后,別的線程來通知我,當(dāng)別的線程通知我之后呢,我還要再check一下這個(gè)條件是否滿足,如果不滿足,還要繼續(xù)進(jìn)入waiting狀態(tài),這樣邏輯上才是比較完備的。

Object.notify
Wakes up a single thread that is waiting on this object"s monitor. If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation. A thread waits on an object"s monitor by calling one of the {@code wait} methods.

喚醒一個(gè)waiting態(tài)的線程,這個(gè)線程呢,必須是用同一把鎖進(jìn)入waiting態(tài)的。

所以,notify方法的通常使用方法為:

synchronized (lock) {
    lock.notify()
}

有人可能要問了:wait和notify不在synchronized里面使用會(huì)怎么樣?
我也好奇了這一點(diǎn),然后實(shí)驗(yàn)了一把,發(fā)現(xiàn)會(huì)拋異常,運(yùn)行時(shí)直接報(bào)錯(cuò)

java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
Thread.join

join方法是一個(gè)實(shí)例方法,先看看文檔的定義:

//Waits for this thread to die.
public final void join() throws InterruptedException

它的意思是,調(diào)用threadA.join()的線程,要進(jìn)入waiting狀態(tài),一直到線程threadA執(zhí)行完畢。
比如

public static void main() {
       Thread t1 = new Thread(…);
       t1.join();
       // 這行代碼必須要等t1全部執(zhí)行完畢,才會(huì)執(zhí)行
}
Thread.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();

這個(gè)方法的意思是,告訴調(diào)度器,當(dāng)前線程愿意放棄cpu的使用,愿意將cpu讓給其它的線程。

用人話說就是:哎呀,我現(xiàn)在已經(jīng)運(yùn)行了那么久了,把機(jī)會(huì)留給別人吧,cpu你快去運(yùn)行一下其他線程吧,我歇一會(huì)。

但是按文檔上的描述,這只是對調(diào)度器的一個(gè)暗示。也就是說,具體會(huì)發(fā)生什么,還要看調(diào)度器是如何處理的。

所以我又來捏造需求了。我們先看看下面的代碼會(huì)發(fā)生什么:

public static void main(String[] arg) {
    
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                count++;
            }
        }
    });

    Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                count++;
            }
        }
    });
    
    t1.start();
    t2.start();
    
}

兩個(gè)線程一起給count自增10000次,由于沒有加鎖,和自增也不是一個(gè)原子操作,這樣就會(huì)導(dǎo)致,兩個(gè)線程都自增10000次,最后count的結(jié)果,一定是小于20000的一個(gè)數(shù)。

等等!等一下!自增不是原子操作是怎么回事,這代碼不是只有一行嗎?

大家都知道代碼最終會(huì)被翻譯為指令,由cpu去執(zhí)行,一條指令是原子的,但是一行代碼被翻譯成多條指令,那么也就會(huì)被多個(gè)線程交替進(jìn)行,這也就是多線程編程常見的問題。

自增的代碼可以用過idea的工具查看到。

GETSTATIC thread/TestThreadFunction.count : I
ICONST_1
IADD
PUTSTATIC thread/TestThreadFunction.count : I

可以看到,它被拆分成了四條執(zhí)行去執(zhí)行。

這個(gè)代碼的執(zhí)行結(jié)果就是,最后的結(jié)果是小于20000的。

那么,我們現(xiàn)在設(shè)計(jì)一下,我希望通過上面提到的方法,讓兩個(gè)線程交替的執(zhí)行,這樣不就可以穩(wěn)定的自增到20000了嗎?

具體怎么做呢,看下面的代碼:

public static int count = 0;

public static final Object object = new Object();

public static void main(String[] arg) {

    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                for (int i = 0; i < 10000; i++) {
                    synchronized (object) {
                        object.notify();
                        object.wait();
                    }
                    count++;
                    System.out.println("t1 " + count);
                }
                synchronized (object) {
                    object.notify();
                }
            } catch (Throwable e) {

            }
        }
    });

    Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                for (int i = 0; i < 10000; i++) {
                    synchronized (object) {
                        object.notify();
                        object.wait();
                    }
                    count++;
                    System.out.println("t2 " + count);
                }
                synchronized (object) {
                    object.notify();
                }
            } catch (Throwable e) {

            }
        }
    });

    t1.start();
    t2.start();

    System.out.println("count: " + count);

}

首先第一個(gè)線程(t1)進(jìn)入了同步鎖object,調(diào)用notify方法,通知?jiǎng)e的線程起來干活,但此時(shí)沒有任何作用,接下來調(diào)用wait,讓自己進(jìn)入waiting狀態(tài)。

接著第二個(gè)線程(t2)自然而然就要干起活來,它先調(diào)用了notify方法,觸發(fā)了一次喚醒,然后調(diào)用wait方法也進(jìn)入了waiting狀態(tài)。

t1收到了notify的喚醒,退出臨界區(qū),開始給count自增,本次循環(huán)結(jié)束,重新notify,wait后進(jìn)入waiting狀態(tài)。

t2被這個(gè)notify所喚醒,開始給count自增,本次循環(huán)結(jié)束,接著重復(fù)一樣的過程。

……

就這樣,兩個(gè)線程交替的執(zhí)行了起來。

最終我們得到的結(jié)果是這樣的:

count: 0
t1 1
t2 2
t1 3
t2 4
t1 5
t2 6
t1 7
t2 8
t1 9
t2 10
t1 11
... // 此處省略
t2 19998
t1 19999
t2 20000

我們發(fā)現(xiàn)一個(gè)問題,就是主線程的最后面的輸出,先執(zhí)行了,輸出了0,這是怎么回事呢?

這是由于兩個(gè)工作線程還沒開始工作,主線程就執(zhí)行完畢了。那么,我們希望在兩個(gè)線程執(zhí)行完畢后,主線程再輸出一下結(jié)果,這個(gè)事情可以做到嗎?

我希望一個(gè)線程工作完畢了,我再繼續(xù)執(zhí)行

這個(gè)不就是join的作用嗎?

于是我們的代碼可以在start兩個(gè)線程后,加上join,再輸出。

...// 這部分相同,省略
t1.start();
t2.start();

try {
    t1.join();
    t2.join();
} catch (InterruptedException e) {
    e.printStackTrace();
}

System.out.println("count: " + count);

這樣的執(zhí)行結(jié)果就是主線程的輸出在最后了。

... // 省略
t1 19997
t2 19998
t1 19999
t2 20000
count: 20000

接下來我們探討一下Thread.yield的實(shí)際作用

先將代碼改寫為下面簡單的,通過synchronized關(guān)鍵字進(jìn)行同步的寫法

Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            synchronized (object) {
                count++;
                System.out.println("t111111 " + count);
            }
        }
    }
});

Thread t2 = new Thread(new Runnable() {
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            synchronized (object) {
                count++;
                System.out.println("t2 " + count);
            }
        }
    }
});

我們可以通過代碼的輸出,觀察到,線程的調(diào)度是非常的緊密的,就是說,總是一段時(shí)間t1一直在執(zhí)行,然后t2再緊密的執(zhí)行一段時(shí)間。

... // 省略
t111111 153
t111111 154
t111111 155
t111111 156
t111111 157
t111111 158
t111111 159
t111111 160
t111111 161
t111111 162
t111111 163
t111111 164
t111111 165
t111111 166
t2 167
t2 168
t2 169
t2 170
t2 171
t2 172
t2 173
t2 174
t2 175
t2 176
t2 177
t2 178
t2 179
t2 180
t2 181
t2 182
... // 省略

t1連續(xù)執(zhí)行了166次,才輪到t2來執(zhí)行。一旦t2開始執(zhí)行,就會(huì)一直搶占cpu一段時(shí)間。

我們現(xiàn)在加上Thread.yield方法試試

for (int i = 0; i < 1000; i++) {
    synchronized (object) {
        count++;
        System.out.println("t2 " + count);
    }
    Thread.yield(); // 加在這里
}

大致的可以看到,線程對cpu的搶占,變得更加謙讓了

t111111 1
t2 2
t2 3
t2 4
t111111 5
t2 6
t2 7
t2 8
t111111 9
t111111 10
t2 11
t2 12
t111111 13
t111111 14
t111111 15
t2 16
t2 17
t2 18
t111111 19
t111111 20
t2 21
t111111 22
t2 23
t2 24
t111111 25
t2 26
t111111 27
t2 28
t111111 29
t111111 30
t2 31
t2 32
... // 省略
總結(jié)

Object.wait讓線程進(jìn)入wait狀態(tài),必須要其他線程喚醒,或者是傳入了時(shí)間長度的wait方法,將至多等待傳入的時(shí)間長度后自動(dòng)喚醒。

Object.notify通知任一一個(gè)進(jìn)入等待狀態(tài)的線程,notifyAll通知所有

Thread.join讓調(diào)用線程阻塞在這個(gè)方法上,直到j(luò)oin的線程完全執(zhí)行完畢,調(diào)用線程才會(huì)繼續(xù)執(zhí)行。

Thread.yield通知調(diào)度器,主動(dòng)讓出對cpu的占用。

如果你喜歡這篇文章,歡迎點(diǎn)贊評論打賞
更多干貨內(nèi)容,歡迎關(guān)注我的公眾號(hào):好奇碼農(nóng)君

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/76009.html

相關(guān)文章

  • Java并發(fā)編程——線程基礎(chǔ)查漏補(bǔ)缺

    摘要:告訴當(dāng)前執(zhí)行的線程為線程池中其他具有相同優(yōu)先級的線程提供機(jī)會(huì)。不能保證會(huì)立即使當(dāng)前正在執(zhí)行的線程處于可運(yùn)行狀態(tài)。當(dāng)達(dá)到超時(shí)時(shí)間時(shí),主線程和是同樣可能的執(zhí)行者候選。下一篇并發(fā)編程線程安全性深層原因 Thread 使用Java的同學(xué)對Thread應(yīng)該不陌生了,線程的創(chuàng)建和啟動(dòng)等這里就不講了,這篇主要講幾個(gè)容易被忽視的方法以及線程狀態(tài)遷移。 wait/notify/notifyAll 首先我...

    luqiuwen 評論0 收藏0
  • @Java | Thread & synchronized - [ 線程 基本使用]

    摘要:線程線程是進(jìn)程中的一個(gè)實(shí)體,作為系統(tǒng)調(diào)度和分派的基本單位。下的線程看作輕量級進(jìn)程。因此,使用的目的是讓相同優(yōu)先級的線程之間能適當(dāng)?shù)妮嗈D(zhuǎn)執(zhí)行。需要注意的是,是線程自己從內(nèi)部拋出的,并不是方法拋出的。 本文及后續(xù)相關(guān)文章梳理一下關(guān)于多線程和同步鎖的知識(shí),平時(shí)只是應(yīng)用層面的了解,由于最近面試總是問一些原理性的知識(shí),雖說比較反感這種理論派,但是為了生計(jì)也必須掌握一番。(PS:并不是說掌握原理不...

    zhunjiee 評論0 收藏0
  • 我的面試準(zhǔn)備過程--線程(更新中)

    摘要:但是,實(shí)際中無法保證達(dá)到讓步目的,因?yàn)樽尣降木€程還有可能被線程調(diào)度程序再次選中。在大多數(shù)情況下,將導(dǎo)致線程從運(yùn)行狀態(tài)轉(zhuǎn)到可運(yùn)行狀態(tài),但有可能沒有效果。 多線程編程 線程狀態(tài)圖 總是無法上傳,稍后上傳 常用函數(shù) 狀態(tài)轉(zhuǎn)換 運(yùn)行中->阻塞 sleep(long millis) 在指定的毫秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線程休眠 join() 等待t線程終止 使用方式 Thread t =...

    zoomdong 評論0 收藏0
  • Java 線程核心技術(shù)梳理(附源碼)

    摘要:本文對多線程基礎(chǔ)知識(shí)進(jìn)行梳理,主要包括多線程的基本使用,對象及變量的并發(fā)訪問,線程間通信,的使用,定時(shí)器,單例模式,以及線程狀態(tài)與線程組。源碼采用構(gòu)建,多線程這部分源碼位于模塊中。通知可能等待該對象的對象鎖的其他線程。 本文對多線程基礎(chǔ)知識(shí)進(jìn)行梳理,主要包括多線程的基本使用,對象及變量的并發(fā)訪問,線程間通信,lock的使用,定時(shí)器,單例模式,以及線程狀態(tài)與線程組。 寫在前面 花了一周時(shí)...

    Winer 評論0 收藏0

發(fā)表評論

0條評論

閱讀需要支付1元查看
<