多線程編程基礎(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ù),這種情況下,其它線程就無(wú)法讀取變量的最新值
解決:
在解決這個(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)存中緩存變量行無(wú)效。
缺點(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();
        }
    }

注意:
注意的是,千萬(wàn)不要忘記調(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)行,有可能等待很長(zhǎng)時(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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容