多線程編程基礎(chǔ)之 wait()狭瞎、notify()、sleep()搏予、join()脚作、yield()、synchronized關(guān)鍵字Lock鎖等

前言:
在面試過程中 關(guān)于多線程編程這一塊是經(jīng)常問到的 為了更好的理解關(guān)于多線程編程基礎(chǔ)特地的記錄此文章把思路理清楚
線程的生命周期

  1. 首先線程一般是這樣創(chuàng)建的:

new Thread(){run(){....}}.start();
new Thread(new Runnable(){run().....}).start();

2.來看一個(gè)經(jīng)典案例 生產(chǎn)者和消費(fèi)者的問題

    static class Product{
        public static String value;
    }
    //生產(chǎn)者線程
    static  class Producer extends Thread{
        @Override
        public void run() {
            while (true){
                if (Product.value==null){
                    Product.value="No"+System.currentTimeMillis();
                    System.out.println("產(chǎn)品:"+Product.value);
                }
            }
        }
    }
    //消費(fèi)者線程
    static  class Consumer extends Thread{
        @Override
        public void run() {
            while (true){
                if (Product.value!=null){
                    Product.value=null;
                    System.out.println("產(chǎn)品已消費(fèi)");
                }
            }
        }
    }
//調(diào)用:
public static void main(String[] args){
        new Producer().start();//開啟生產(chǎn)
        new Consumer().start();//開啟消費(fèi)
    }
產(chǎn)品:null
產(chǎn)品已消費(fèi)
產(chǎn)品:null
產(chǎn)品:No1492225732628
產(chǎn)品已消費(fèi)
產(chǎn)品已消費(fèi)
產(chǎn)品:No1492225732628
產(chǎn)品:No1492225732629

thread

讀操作會(huì)優(yōu)先讀取工作內(nèi)存的數(shù)據(jù)缔刹,如果工作內(nèi)存中不存在球涛,則從主內(nèi)存中拷貝一份數(shù)據(jù)到工作內(nèi)存中;寫操作只會(huì)修改工作內(nèi)存的副本數(shù)據(jù)校镐,這種情況下亿扁,其它線程就無法讀取變量的最新值
解決:
在解決這個(gè)問題的時(shí)候先來了解下Java中關(guān)于多線程中使用的一些關(guān)鍵字和一些方法的作用

關(guān)鍵字 作用
volatile 線程操作變量可見
Lock Java6.0增加的線程同步鎖
synchronized 線程同步鎖
wait() 讓該線程處于等待狀態(tài)
notify() 喚醒處于wait的線程
notifyAll() 喚醒所有處于wait狀態(tài)的線程
sleep() 線程休眠
join() 使當(dāng)線程處于阻塞狀態(tài)
yield() 讓出該線程的時(shí)間片給其他線程

注意:
1. wait()、notify()鸟廓、notifyAll()都必須在synchronized中執(zhí)行从祝,否則會(huì)拋出異常
2. wait()、notify()引谜、notifyAll()都是屬于超類Object的方法
2. 一個(gè)對(duì)象只有一個(gè)鎖(對(duì)象鎖和類鎖還是有區(qū)別的)

一. 使用volatile關(guān)鍵字:

 static class Product{
        //添加volatile關(guān)鍵字
        public volatile static String value;
    }
//調(diào)用
public static void main(String[] args){
        new Producer().start();//開啟生產(chǎn)
        new Consumer().start();//開啟消費(fèi)
    }
產(chǎn)品:No1492229533263
產(chǎn)品已消費(fèi)
產(chǎn)品:No1492229533263
產(chǎn)品已消費(fèi)
產(chǎn)品:No1492229533263
產(chǎn)品已消費(fèi)
產(chǎn)品:No1492229533263
產(chǎn)品已消費(fèi)
產(chǎn)品:No1492229533263
產(chǎn)品已消費(fèi)
產(chǎn)品:No1492229533263
產(chǎn)品已消費(fèi)

Volatile: 保證了不同線程對(duì)這個(gè)變量進(jìn)行操作時(shí)的可見性牍陌,即一個(gè)線程修改了某個(gè)變量的值,這新值對(duì)其他線程來說是立即可見的员咽。volatile關(guān)鍵字會(huì)強(qiáng)制將修改的值立即寫入主存毒涧,使線程的工作內(nèi)存中緩存變量行無效。
缺點(diǎn):但是不具備原子特性贝室。這就是說線程能夠自動(dòng)發(fā)現(xiàn) volatile 變量的最新值契讲。Volatile 變量可用于提供線程安全,但是只能應(yīng)用于非常有限的一組用例:多個(gè)變量之間或者某個(gè)變量的當(dāng)前值與修改后值之間沒有約束滑频。
二. 使用synchronized線程同步鎖

static class Product{
        public static String value;
    }
//生產(chǎn)者線程
    static  class Producer extends Thread{
        Object object;
        public Producer(Object object) {
            this.object = object;
        }
        @Override
        public void run() {
            while (true) {
                synchronized (object) {
                    if (Product.value == null) {
                        Product.value = "No" + System.currentTimeMillis();
                        System.out.println("產(chǎn)品:" + Product.value);
                    }
                }
            }
        }
    }
//消費(fèi)者線程
    static  class Consumer extends Thread{
        Object object;
        public Consumer(Object object) {
            this.object=object;
        }
        @Override
        public void run() {
            while (true) {
                synchronized (object) {
                    if (Product.value != null) {
                        Product.value = null;
                        System.out.println("產(chǎn)品已消費(fèi)");
                    }
                }
            }
        }
    }
//調(diào)用
public static void main(String[] args){
        Object object=new Object();
        new Producer(object).start();//開啟生產(chǎn)
        new Consumer(object).start();//開啟消費(fèi)
    }
產(chǎn)品:No1492244495050
產(chǎn)品已消費(fèi)
產(chǎn)品:No1492244495050
產(chǎn)品已消費(fèi)
產(chǎn)品:No1492244495050
產(chǎn)品已消費(fèi)
產(chǎn)品:No1492244495050
產(chǎn)品已消費(fèi)
產(chǎn)品:No1492244495050

上面通過synchronized對(duì)Object object=new Object();加鎖 也叫對(duì)象鎖 實(shí)現(xiàn)鎖的互斥捡偏,當(dāng)生產(chǎn)線程生產(chǎn)產(chǎn)品的時(shí)候會(huì)對(duì)object加鎖 消費(fèi)者線程會(huì)進(jìn)入阻塞狀態(tài) 直到生產(chǎn)線程完成產(chǎn)品的生產(chǎn)釋放鎖 反之也是同樣的道理。但是這樣只能被動(dòng)的喚醒線程的執(zhí)行 可以使用wait和notify來進(jìn)行主動(dòng)喚醒線程繼續(xù)執(zhí)行

//生產(chǎn)者線程
    static class Producer extends Thread {
        Object object;
        public Producer(Object object) {
            this.object = object;
        }
        @Override
        public void run() {
            while (true) {
                //對(duì)象鎖
                synchronized (object) {
                    if (Product.value != null) {
                        try {
                            object.wait();//產(chǎn)品還未消費(fèi) 進(jìn)入等待狀態(tài)
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    Product.value = "No" + System.currentTimeMillis();
                    System.out.println("產(chǎn)品:" + Product.value);
                    object.notify();//產(chǎn)品已生產(chǎn) 喚醒消費(fèi)者線程
                }
            }
        }
    }
//消費(fèi)者線程
    static class Consumer extends Thread {
        Object object;
        public Consumer(Object object) {
            this.object = object;
        }
        @Override
        public void run() {
            while (true) {
                synchronized (object) {
                    if (Product.value == null) {
                        try {
                            object.wait();//產(chǎn)品為空 進(jìn)入等待狀態(tài)
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    Product.value = null;
                    System.out.println("產(chǎn)品已消費(fèi)");
                    object.notify();//產(chǎn)品已經(jīng)消費(fèi) 喚醒生產(chǎn)者線程生產(chǎn)
                }
            }
        }
    }
public static void main(String[] args){
        Object object=new Object();
        new Producer(object).start();//開啟生產(chǎn)
        new Consumer(object).start();//開啟消費(fèi)
    }
產(chǎn)品:No1492246274190
產(chǎn)品已消費(fèi)
產(chǎn)品:No1492246274190
產(chǎn)品已消費(fèi)
產(chǎn)品:No1492246274190
產(chǎn)品已消費(fèi)

通過添加wait notify關(guān)鍵字去主動(dòng)喚醒生產(chǎn)者或消費(fèi)者線程的執(zhí)行
三.線程同步之ReentrantLock鎖
與synchronized關(guān)鍵字類似的同步功能峡迷,只是在使用時(shí)需要顯式地獲取和釋放鎖银伟,缺點(diǎn)就是缺少像synchronized那樣隱式獲取釋放鎖的便捷性,但是卻擁有了鎖獲取與釋放的可操作性绘搞,可中斷的獲取鎖以及超時(shí)獲取鎖等多種synchronized關(guān)鍵字所不具備的同步特性彤避。

Lock lock=new ReentrantLock();
public void setSpData(String name){
        lock.lock();
        try {
            //線程同步操作 比如IO讀寫 等
        }finally {
            lock.unlock();
        }
    }

注意:
注意的是,千萬不要忘記調(diào)用unlock來釋放鎖看杭,否則可能會(huì)引發(fā)死鎖等問題,
而用synchronized忠藤,JVM將確保鎖會(huì)獲得自動(dòng)釋放,這也是為什么Lock沒有完全替代掉synchronized的原因
四.sleep(),join(),yield()的使用

sleep()讓線程休息指定的時(shí)間楼雹,時(shí)間一到就繼續(xù)運(yùn)行

 new Thread(){
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);//休眠3秒 毫秒為單位
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();

join()讓指定的線程先執(zhí)行完再執(zhí)行其他線程模孩,而且會(huì)阻塞主線程

 static class B extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println("線程一:" + 1);
            }
        }
    }
static class A extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println("線程二:" + 1);
            }
        }
    }
public static void main(String[] args) {
        A a = new A();
        B b = new B();
        try {
            a.start();//啟動(dòng)線程
            a.join();//join 該線程優(yōu)先執(zhí)行 其他線程進(jìn)入等待
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            b.start();//啟動(dòng)線程
            b.join();//join 該線程優(yōu)先執(zhí)行 其他線程進(jìn)入等待
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
線程二:0
線程二:1
線程二:2
線程二:3
線程二:4
線程一:0
線程一:1
線程一:2
線程一:3
線程一:4

yield()將指定線程先禮讓一下別的線程的先執(zhí)行

注意:yield()會(huì)禮讓給相同優(yōu)先級(jí)的或者是優(yōu)先級(jí)更高的線程執(zhí)行尖阔,不過yield()這個(gè)方法只是把線程的執(zhí)行狀態(tài)打回準(zhǔn)備就緒狀態(tài),所以執(zhí)行了該方法后榨咐,有可能馬上又開始運(yùn)行介却,有可能等待很長時(shí)間

static class B extends Thread {
        String name;
        public B(String name) {
            this.name=name;
        }
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println(name + i);
                if(i==3){
                    System.out.println("將時(shí)間片禮讓給別的線程");
                    Thread.yield();
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
public static void main(String[] args) {
        new B("線程一:").start();
        new B("線程二:").start();
    }
線程一:0
線程一:1
線程一:2
線程一:3
將時(shí)間片禮讓給別的線程
線程二:0
線程二:1
線程二:2
線程二:3
將時(shí)間片禮讓給別的線程
線程二:4
線程一:4

其他:

  1. 線程優(yōu)先級(jí):通過setPriority(int priority)設(shè)置線程優(yōu)先級(jí)提高線程獲取時(shí)間的幾率(這只是提高線程優(yōu)先獲取時(shí)間片的幾率 而不是肯定優(yōu)先執(zhí)行) getPriority()獲取線程的優(yōu)先級(jí) 最高為10 最低為1 默認(rèn)為5
public static void main(String[] args) {
        A a = new A("線程一:");
        B b = new B("線程二:");
        a.setPriority(3);
        b.setPriority(10);
        a.start();
        b.start();
    }
  1. 守護(hù)線程:前面所講的是用戶線程 其實(shí)還有一個(gè)守護(hù)線程,起作:用只要當(dāng)前JVM實(shí)例中尚存在任何一個(gè)非守護(hù)線程沒有結(jié)束,守護(hù)線程就全部工作块茁;只有當(dāng)最后一個(gè)非守護(hù)線程結(jié)束時(shí)齿坷,守護(hù)線程隨著JVM一同結(jié)束工作。守護(hù)線程的作用是為其他線程的運(yùn)行提供便利服務(wù)数焊,守護(hù)線程最典型的應(yīng)用就是 GC (垃圾回收器)永淌,它就是一個(gè)很稱職的守護(hù)者,在編寫程序時(shí)也可以自己設(shè)置守護(hù)線程佩耳。
static class B extends Thread {
        String name;
        public B(String name) {
            this.name=name;
        }
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println(name + i);
            }
        }
    }
    static class A extends Thread {
        String name;
        public A(String name) {
            this.name=name;
        }
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                System.out.println(name + i);
            }
        }
    }
public static void main(String[] args) {
        A a = new A("守護(hù)線程執(zhí)行:");
        B b = new B("用戶線程執(zhí)行:");
        a.setDaemon(true);//設(shè)置a線程為守護(hù)線程 必須在start()前設(shè)置 不然或有異常
        a.isDaemon();//判斷a線程是否為守護(hù)線程
        a.start();
        b.start();
    }
守護(hù)線程執(zhí)行:0
守護(hù)線程執(zhí)行:1
守護(hù)線程執(zhí)行:2
守護(hù)線程執(zhí)行:3
守護(hù)線程執(zhí)行:4
守護(hù)線程執(zhí)行:5
守護(hù)線程執(zhí)行:6
守護(hù)線程執(zhí)行:7
守護(hù)線程執(zhí)行:8
用戶線程執(zhí)行:0
用戶線程執(zhí)行:1
用戶線程執(zhí)行:2
用戶線程執(zhí)行:3
用戶線程執(zhí)行:4
守護(hù)線程執(zhí)行:9
守護(hù)線程執(zhí)行:10
守護(hù)線程執(zhí)行:11
守護(hù)線程執(zhí)行:12
守護(hù)線程執(zhí)行:13
守護(hù)線程執(zhí)行:14
守護(hù)線程執(zhí)行:15
守護(hù)線程執(zhí)行:16
守護(hù)線程執(zhí)行:17
Process finished with exit code 0

當(dāng)用戶線程執(zhí)行完畢后 JVM虛擬了退出前 守護(hù)線程隨著JVM一同結(jié)束工作
設(shè)置線程為守護(hù)線程 必須在start()前設(shè)置 不然或有異常

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末遂蛀,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子干厚,更是在濱河造成了極大的恐慌李滴,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛮瞄,死亡現(xiàn)場(chǎng)離奇詭異所坯,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)挂捅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門芹助,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人籍凝,你說我怎么就攤上這事周瞎∶缢酰” “怎么了饵蒂?”我有些...
    開封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長酱讶。 經(jīng)常有香客問我退盯,道長,這世上最難降的妖魔是什么泻肯? 我笑而不...
    開封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任渊迁,我火速辦了婚禮,結(jié)果婚禮上灶挟,老公的妹妹穿的比我還像新娘琉朽。我一直安慰自己,他們只是感情好稚铣,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開白布箱叁。 她就那樣靜靜地躺著墅垮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪耕漱。 梳的紋絲不亂的頭發(fā)上算色,一...
    開封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音螟够,去河邊找鬼灾梦。 笑死,一個(gè)胖子當(dāng)著我的面吹牛妓笙,可吹牛的內(nèi)容都是我干的若河。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼寞宫,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼牡肉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起淆九,我...
    開封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤统锤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后炭庙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體饲窿,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年焕蹄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了逾雄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡腻脏,死狀恐怖鸦泳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情永品,我是刑警寧澤做鹰,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站鼎姐,受9級(jí)特大地震影響钾麸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜炕桨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一饭尝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧献宫,春花似錦钥平、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奈惑。三九已至,卻和暖如春睡汹,著一層夾襖步出監(jiān)牢的瞬間肴甸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來泰國打工囚巴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留原在,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓彤叉,卻偏偏與公主長得像庶柿,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子秽浇,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容