多線程編程基礎之 wait()、notify()、sleep()、join()、yield()、synchronized關鍵字Lock鎖等

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

  1. 首先線程一般是這樣創建的:

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

2.來看一個經典案例 生產者和消費者的問題

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

thread

讀操作會優先讀取工作內存的數據,如果工作內存中不存在,則從主內存中拷貝一份數據到工作內存中;寫操作只會修改工作內存的副本數據,這種情況下,其它線程就無法讀取變量的最新值
解決:
在解決這個問題的時候先來了解下Java中關于多線程中使用的一些關鍵字和一些方法的作用

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

注意:
1. wait()、notify()、notifyAll()都必須在synchronized中執行,否則會拋出異常
2. wait()、notify()、notifyAll()都是屬于超類Object的方法
2. 一個對象只有一個鎖(對象鎖和類鎖還是有區別的)

一. 使用volatile關鍵字:

 static class Product{
        //添加volatile關鍵字
        public volatile static String value;
    }
//調用
public static void main(String[] args){
        new Producer().start();//開啟生產
        new Consumer().start();//開啟消費
    }
產品:No1492229533263
產品已消費
產品:No1492229533263
產品已消費
產品:No1492229533263
產品已消費
產品:No1492229533263
產品已消費
產品:No1492229533263
產品已消費
產品:No1492229533263
產品已消費

Volatile: 保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。volatile關鍵字會強制將修改的值立即寫入主存,使線程的工作內存中緩存變量行無效。
缺點:但是不具備原子特性。這就是說線程能夠自動發現 volatile 變量的最新值。Volatile 變量可用于提供線程安全,但是只能應用于非常有限的一組用例:多個變量之間或者某個變量的當前值與修改后值之間沒有約束。
二. 使用synchronized線程同步鎖

static class Product{
        public static String value;
    }
//生產者線程
    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("產品:" + Product.value);
                    }
                }
            }
        }
    }
//消費者線程
    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("產品已消費");
                    }
                }
            }
        }
    }
//調用
public static void main(String[] args){
        Object object=new Object();
        new Producer(object).start();//開啟生產
        new Consumer(object).start();//開啟消費
    }
產品:No1492244495050
產品已消費
產品:No1492244495050
產品已消費
產品:No1492244495050
產品已消費
產品:No1492244495050
產品已消費
產品:No1492244495050

上面通過synchronizedObject object=new Object();加鎖 也叫對象鎖 實現鎖的互斥,當生產線程生產產品的時候會對object加鎖 消費者線程會進入阻塞狀態 直到生產線程完成產品的生產釋放鎖 反之也是同樣的道理。但是這樣只能被動的喚醒線程的執行 可以使用wait和notify來進行主動喚醒線程繼續執行

//生產者線程
    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) {
                        try {
                            object.wait();//產品還未消費 進入等待狀態
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    Product.value = "No" + System.currentTimeMillis();
                    System.out.println("產品:" + Product.value);
                    object.notify();//產品已生產 喚醒消費者線程
                }
            }
        }
    }
//消費者線程
    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();//產品為空 進入等待狀態
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    Product.value = null;
                    System.out.println("產品已消費");
                    object.notify();//產品已經消費 喚醒生產者線程生產
                }
            }
        }
    }
public static void main(String[] args){
        Object object=new Object();
        new Producer(object).start();//開啟生產
        new Consumer(object).start();//開啟消費
    }
產品:No1492246274190
產品已消費
產品:No1492246274190
產品已消費
產品:No1492246274190
產品已消費

通過添加wait notify關鍵字去主動喚醒生產者或消費者線程的執行
三.線程同步之ReentrantLock鎖
與synchronized關鍵字類似的同步功能,只是在使用時需要顯式地獲取和釋放鎖,缺點就是缺少像synchronized那樣隱式獲取釋放鎖的便捷性,但是卻擁有了鎖獲取與釋放的可操作性,可中斷的獲取鎖以及超時獲取鎖等多種synchronized關鍵字所不具備的同步特性。

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

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

sleep()讓線程休息指定的時間,時間一到就繼續運行

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

join()讓指定的線程先執行完再執行其他線程,而且會阻塞主線程

 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();//啟動線程
            a.join();//join 該線程優先執行 其他線程進入等待
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            b.start();//啟動線程
            b.join();//join 該線程優先執行 其他線程進入等待
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
線程二:0
線程二:1
線程二:2
線程二:3
線程二:4
線程一:0
線程一:1
線程一:2
線程一:3
線程一:4

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

注意:yield()會禮讓給相同優先級的或者是優先級更高的線程執行,不過yield()這個方法只是把線程的執行狀態打回準備就緒狀態,所以執行了該方法后,有可能馬上又開始運行,有可能等待很長時間

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("將時間片禮讓給別的線程");
                    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
將時間片禮讓給別的線程
線程二:0
線程二:1
線程二:2
線程二:3
將時間片禮讓給別的線程
線程二:4
線程一:4

其他:

  1. 線程優先級:通過setPriority(int priority)設置線程優先級提高線程獲取時間的幾率(這只是提高線程優先獲取時間片的幾率 而不是肯定優先執行) getPriority()獲取線程的優先級 最高為10 最低為1 默認為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. 守護線程:前面所講的是用戶線程 其實還有一個守護線程,起作:用只要當前JVM實例中尚存在任何一個非守護線程沒有結束,守護線程就全部工作;只有當最后一個非守護線程結束時,守護線程隨著JVM一同結束工作。守護線程的作用是為其他線程的運行提供便利服務,守護線程最典型的應用就是 GC (垃圾回收器),它就是一個很稱職的守護者,在編寫程序時也可以自己設置守護線程。
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("守護線程執行:");
        B b = new B("用戶線程執行:");
        a.setDaemon(true);//設置a線程為守護線程 必須在start()前設置 不然或有異常
        a.isDaemon();//判斷a線程是否為守護線程
        a.start();
        b.start();
    }
守護線程執行:0
守護線程執行:1
守護線程執行:2
守護線程執行:3
守護線程執行:4
守護線程執行:5
守護線程執行:6
守護線程執行:7
守護線程執行:8
用戶線程執行:0
用戶線程執行:1
用戶線程執行:2
用戶線程執行:3
用戶線程執行:4
守護線程執行:9
守護線程執行:10
守護線程執行:11
守護線程執行:12
守護線程執行:13
守護線程執行:14
守護線程執行:15
守護線程執行:16
守護線程執行:17
Process finished with exit code 0

當用戶線程執行完畢后 JVM虛擬了退出前 守護線程隨著JVM一同結束工作
設置線程為守護線程 必須在start()前設置 不然或有異常

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容