『JavaWeb』線程

本篇博客主要介紹Java中線程的相關概念。

什麼是線程?


線程是操作系統能夠進行運算調度的最小單位。它被包含在進程之中是進程中的實際運作單位。一個線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程每條線程並行執行不同的任務。所以線程可以理解爲進程中的一個執行流進程可以理解爲線程組

  • 進程是資源分配的最小單位
  • 線程是CPU調度的最小單位

同一進程中的不同線程將共享該進程中的全部資源,如虛擬地址空間,文件描述符表和信號處理等但同一進程中的多個線程有各自的調用棧(call stack),自己的寄存器環境等

下面,我們通過代碼來感受一下什麼是線程

public class ThreadTest {
    private static class MyThread extends Thread {
        @Override
        public void run() {
            while (true) {
                System.out.println(this.getName() + " is running!");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread t1 = new MyThread();
        t1.start();

        MyThread t2 = new MyThread();
        t2.start();

        MyThread t3 = new MyThread();
        t3.start();

        while (true) {
            System.out.println(Thread.currentThread().getName() + " is running!");
            Thread.sleep(3000);
        }
    }
}

在這裏插入圖片描述
我們可以使用jconsole工具來觀察一個Java程序的線程情況,jconsole工具在JDK安裝目錄下的bin文件中,由於我們在配置Java環境的時候,已經將這個路徑添加到環境變量,所以我們可以直接使用win + r進行操作,然後輸入jconsole即可
在這裏插入圖片描述
在這裏插入圖片描述
可以看到除了main、Thread-0、Thread-1、Thread-2和Thread-3這些線程之外還有很多的其他線程,這些都是JVM啓動的一些守護線程

多線程的優勢


我們來看一個代碼,觀察一下多線程在某些場合的優勢

public class ThreadTest {
    private static final long COUNT = 10_0000_0000L;

    public static void main(String[] args) throws InterruptedException {
        concurrency();

        serial();
    }

    /**
     * 併發
     * @throws InterruptedException
     */
    private static void concurrency() throws InterruptedException {
        long begin = System.currentTimeMillis();

        // 利用一個線程計算a的值
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                int a = 0;
                for (long i = 0; i < COUNT; ++i) {
                    --a;
                }
            }
        });
        thread.start();

        // 主線程內計算b的值
        int b = 0;
        for (long i = 0; i < COUNT; ++i) {
            --b;
        }
        // 等待thread線程運行結束
        thread.join();

        long end = System.currentTimeMillis();

        System.out.printf("併發: %d毫秒%n", end - begin);
    }

    /**
     * 串行
     */
    private static void serial() {
        long begin = System.currentTimeMillis();

        int a = 0;
        for (long i = 0; i < COUNT; ++i) {
            --a;
        }

        int b = 0;
        for (long i = 0; i < COUNT; ++i) {
            --b;
        }

        long end = System.currentTimeMillis();

        System.out.printf("串行: %d毫秒%n", end - begin);
    }
}

在這裏插入圖片描述
可以看出併發還是比串行要快一些的

線程的創建


繼承Thread類


可以通過繼承Thread類來創建一個線程類,該方法的好處是this代表的就是當前線程,不需要通過Thread.currentThread()來獲取當前線程的引用。
我們來看一下代碼

public class ThreadCreate {
    public static void main(String[] args) {
        MyThread thread = new MyThread("我的線程");
        thread.start();
    }
}

class MyThread extends Thread {
    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        while (true) {
            System.out.println(this.getName() + " is running!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

在這裏插入圖片描述
我們還可以以匿名類的方式來實現

public class ThreadCreate {
    public static void main(String[] args) {
        Thread thread = new Thread("我的線程") {
            @Override
            public void run() {
                while (true) {
                    try {
                        System.out.println(this.getName() + " is running!");
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        thread.start();
    }
}

在這裏插入圖片描述

實現Runnable接口


通過實現Runnable接口,並且調用Thread的構造方法時將Runnable對象作爲target參數傳入來創建線程對象。該方法的好處是可以規避類的單繼承的限制;但需要通過Thread.currentThread()來獲取當前線程的引用
下面看一下代碼

public class ThreadCreate {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable(), "我的線程");
        thread.start();
    }
}

class MyRunnable implements Runnable {
    @Override
    public void run() {
        while (true) {
            try {
                System.out.println(Thread.currentThread().getName() + " is running!");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

在這裏插入圖片描述
除了上述實現一個MyRunnable類創建實例之外,我們還可以使用匿名類的方式來實現,下面看代碼實現:

public class ThreadCreate {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        System.out.println(Thread.currentThread().getName() + " is running!");
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "我的線程");
        thread.start();
    }
}

在這裏插入圖片描述
還可以使用Lambda表達式來創建Runnable子類對象

public class ThreadCreate {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            while (true) {
                try {
                    System.out.println(Thread.currentThread().getName() + " is running!");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "我的線程");
        thread.start();
    }
}

在這裏插入圖片描述

Thread類常用方法


Thread類是JVM用來管理線程的一個類,換句話說,每個線程都有一個唯一的Thread對象與之關聯
每個執行流,也需要一個對象來描述,而Thread類的對象就是用來描述一個線程執行流的,JVM會將這些Thread對象組織起來,用於線程調度,線程管理

Thread的常見構造方法


方法 說明
Thread() 創建線程對象
Thread(Runnable target) 使用Runnable對象創建線程對象
Thread(String name) 創建線程對象,並命名
Thread(Runnable target, String name) 使用Runnable對象創建線程對象,並命名
Thread(ThreadGroup group, Runnable target) 線程可以被用來分組管理,分號的組即線程組

Thread的幾個常見的屬性


屬性 獲取方法
ID getID()
名稱 getName()
狀態 getState()
優先級 getPriority()
是否後臺線程 isDaemon()
是否存活 isAlive()
是否被中斷 isInterrupted()
  • 關於後臺線程,需要記住一點:JVM會在一個進程的所有非後臺線程結束後,纔會結束運行
  • 是否存活:可以簡單的理解爲run方法是否運行結束了

啓動一個線程


我們可以通過覆寫run方法來創建一個線程對象,但線程對象被創建出來並不意味着線程就開始運行了

  • 覆寫run()方法是提供給線程要做的事情
  • 調用start()方法,線程才真正獨立去執行了

一定要區分run和start方法的區別,下面我們通過代碼來演示一下

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread("我的線程") {
            @Override
            public void run() {
                while (true) {
                    try {
                        System.out.println("我的線程");
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        thread.run();

        while (true) {
            System.out.println("main");
            Thread.sleep(1000);
        }
    }
}

在這裏插入圖片描述
我們用jconsole來觀察一下
在這裏插入圖片描述
可以看到只有一個main線程,我們創建的線程名爲“我的線程”的線程並沒有被啓動。thread.run();就是在調用thread對象的方法run();並不是在啓動線程

下面,我們來看一下真正的啓動線程

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread("我的線程") {
            @Override
            public void run() {
                while (true) {
                    try {
                        System.out.println("我的線程");
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        thread.start();

        while (true) {
            System.out.println("main");
            Thread.sleep(1000);
        }
    }
}

在這裏插入圖片描述
同樣的,我們使用jconsole來觀察一下
在這裏插入圖片描述
可以看到,我的線程和main線程都運行起來了

中斷一個線程


中斷一個線程有兩種方式

  • 通過共享的標記來進行溝通
  • 通過調用interrupt()方法來通知

我們先來看一下通過共享的標記進行溝通的方法

public class ThreadDemo {
    private static boolean flag= false;

    public static void main(String[] args) throws InterruptedException {
        new Thread("我的線程") {
            @Override
            public void run() {
                while (!flag) {
                    try {
                        System.out.println(Thread.currentThread().getName() + " is running!");
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();

        Thread.sleep(5000);
        flag= true;
    }
}

在這裏插入圖片描述
但是這種方式有一個小問題,那就是如果目標線程正在sleep(),那麼目標線程不會被立即中斷,而是等sleep()結束後纔會結束中斷

通過調用實例方法或者靜態方法來中斷一個線程
我們來看一下三個方法:

方法 說明
public void interrupt() 中斷對象關聯的線程,如果線程處於阻塞狀態,則以異常的方式通知,否則設置標誌位
public static boolean interrupted() 判斷當前線程的中斷標誌位是否被設置,調用後清除標誌位
public boolean isInterrupted() 判斷當前對象關聯的線程的標誌位是否被設置,調用後不清除標誌位

interrupt()方法比較好理解,就是中斷對象關聯的線程
下面,我們來看一下interrupted()和isInterrupted()的區別
在這裏插入圖片描述
在這裏插入圖片描述

  • 可以看到interrupted()方法內部調用的就是isInterrupted()方法,但是interrupted()會清除標誌位,isInterrupted()不會清除標誌位
  • interrupted()是靜態方法,isInterrupted()是實例方法

對於這三個方法,我們可以理解爲底層也是通過一個共享的標誌位flag來實現的isInterrupted()方法只是返回這個flag的值,而interrupted()不僅返回這個flag的值,還會將這個flag的值置爲false

我們再來看一下interrupt()方法

  • 通過thread對象調用interrupt()方法通知該線程停止運行
  • thread收到通知的方式有兩種
    ① 如果線程調用了wait/join/sleep等方法而阻塞掛起,則以InterruptedException異常的形式通知,清除中斷標誌
    否則,只是內部的一箇中斷標誌被設置,thread可以通過Thread.interrupted()方法或者thread.isInterrupt()方法來判斷當前線程的中斷標誌是否被設置。其中前者在調用之後會清除中斷標誌

下面,我們來看一個代碼

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                while (!Thread.interrupted()) {
                    try {
                        System.out.println(Thread.currentThread().getName() + " is running!");
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        Thread t2 = new Thread() {
            @Override
            public void run() {
                try {
                    while (!Thread.interrupted()) {
                        System.out.println(Thread.currentThread().getName() + " is running!");
                        Thread.sleep(1000);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

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

        Thread.sleep(3000);

        t1.interrupt();
        t2.interrupt();
    }
}

在這裏插入圖片描述
可以看到try…catch和while的位置不同,兩個線程表現出來的結果是不同的

線程等待


有時候,我們需要等待一個線程完成它的工作後,才能進行自己下一步工作。這時候我們就需要一個方法等待線程的結束。
常用的線程等待的方法如下

方法 說明
public void join() 等待線程結束
public void join(long millis) 等待線程結束,最多等millis毫秒
public void join(long millis, int nanos) 等待線程結束,最多等millis毫秒nanos納秒

我們來看一個代碼,該代碼可以實現threads數組中的線程挨個順序執行

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[5];
        for (int i = 0; i < threads.length; ++i) {
            threads[i] = new Thread() {
                @Override
                public void run() {
                    for (int i = 0; i < 2; ++i) {
                        System.out.println(Thread.currentThread().getName());
                    }
                }
            };
        }

        for (int i = 0; i < threads.length; ++i) {
            threads[i].start();
            threads[i].join();
        }
    }
}

在這裏插入圖片描述
如果我們把threads[i].join()這句代碼去掉會怎麼樣,我們來看一下:

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[5];
        for (int i = 0; i < threads.length; ++i) {
            threads[i] = new Thread() {
                @Override
                public void run() {
                    for (int i = 0; i < 2; ++i) {
                        System.out.println(Thread.currentThread().getName());
                    }
                }
            };
        }

        for (int i = 0; i < threads.length; ++i) {
            threads[i].start();
        }
    }
}

在這裏插入圖片描述
可以看到去掉join()順序就亂了

線程的狀態


線程的狀態是一個枚舉類型Thread.State,我們來看一下:

public class ThreadDemo {
    public static void main(String[] args) {
        for (Thread.State state : Thread.State.values()) {
            System.out.println(state);
        }
    }
}

在這裏插入圖片描述
在這裏插入圖片描述

  • 初始狀態(NEW)只是安排了工作,還未開始行動
  • 運行中(RUNNABLE)CPU正在調度該線程
  • 就緒(READY)等待被系統調度
  • 等待(WAITING)、超時等待(TIMED_WAITING)、阻塞(BLOCKED)排隊等着其他事情
  • 終止(TERMINATED)工作全部完成

線程的超時等待、阻塞和等待都對應操作系統中進程的阻塞狀態。只不過Java中對阻塞狀態進行了細分。劃分出了三種

線程安全


我們來看一個例子來體會一下什麼是線程不安全
我們啓動20個線程來對同一個變量進行自增操作,每個線程自增一萬次

public class ThreadDemo {
    private static int num = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[20];
        for (int i = 0; i < threads.length; ++i) {
            threads[i] = new Thread() {
                @Override
                public void run() {
                    for (int i = 0; i < 10000; ++i) {
                        ++num;
                    }
                }
            };
        }

        for (int i = 0; i < threads.length; ++i) {
            threads[i].start();
        }

        while (Thread.activeCount() > 1) {
            Thread.yield();
        }

        System.out.println("num = " + num);
    }
}

在這裏插入圖片描述
我們預期的結果是20萬,但是這裏只有17萬多。這種現象我們可以稱之爲線程不安全,爲什麼會出現線程不安全的問題呢?

線程不安全的原因


原子性


我們把一段代碼想像成一個房間,每個線程就是要進入這個房間的人。如果沒有任何機制保證,A進入房間之後,還沒有出來;B是不是也可以進入房間,打斷A在房間裏的隱私。這個就是不具備原子性的。
如何解決這個問題呢?我們可以給房間加一把鎖,A進去把門鎖上,其他人就進不來了,這樣我們就保證了這段代碼的原子性了
有時也把這個現象叫做同步互斥,表示操作是互相排斥的

一條Java語句不一定是原子的,也不一定只是一條指令。
比如剛纔我們看到的++num,其實是由三步操作組成的

  1. 從內存中把數據讀到CPU
  2. 進行數據更新
  3. 把數據寫回到內存中

不保證原子性會給多線程帶來什麼問題
如果一個線程正在對一個變量進行操作,中途其他線程插入進來了,如果這個操作被打斷了,結果就可能是錯誤的

可見性


JVM將內存組織爲主內存和工作內存兩部分

  • 主內存包括本地方法區和堆
  • 工作內存每個線程都有一個工作內存,工作內存中主要包括兩個部分,一個是屬於該線程私有的棧和對主存部分變量拷貝的寄存器

所有的變量都存儲在主內存中,對於所有線程都是共享的;線程之間無法直接訪問對方的工作內存中的變量,線程間變量的傳遞均需要通過主內存來完成
在這裏插入圖片描述
JVM執行過程中,共享變量在多線程之間不能及時看到改變,這個就是可見性問題

有序性


一段代碼邏輯如下

  1. 去前臺取U盤;
  2. 去教室寫10分鐘作業;
  3. 去前臺取下快遞。

如果是在單線程情況下,JVM、CPU指令集會對其進行優化,比如,按照1->3->2的方式執行,也是沒問題的,可以少跑一次前臺。這種叫做指令重排序
但是如果在多線程場景下就有問題了,可能快遞是在你寫作業的10分鐘內被另一個線程放過來的,或者被人變過了,如果指令重排序,代碼就會是錯誤的。

有序性:如果在線程內觀察,所有操作都是有序的;如果在一個線程中觀察另一個線程,所有操作都是無序的。前半句指:線程內表現爲串行語義,後半句是指:指令重排現象和工作內存與主內存同步延遲現象。

synchronized關鍵字


對於前面的線程不安全的問題,我們可以使用synchronized關鍵字來實現線程安全
synchronized的用法

  • synchronized修飾普通方法,此時鎖的是當前實例的對象
  • synchronized修飾靜態方法,此時鎖的是類的class對象
  • synchronized修飾代碼塊,此時鎖的是括號內的對象

我們分別使用synchronized關鍵字來解決一下前面的線程不安全問題
靜態同步方法

public class ThreadDemo {
    private static int num = 0;

    public static synchronized void increment() {
        ++num;
    }

    public static void main(String[] args) {
        Thread[] threads = new Thread[20];
        for (int i = 0; i < threads.length; ++i) {
            threads[i] = new Thread() {
                @Override
                public void run() {
                    for (int i = 0; i < 10000; ++i) {
                        increment();
                    }
                }
            };
        }

        for (int i = 0; i < threads.length; ++i) {
            threads[i].start();
        }

        while (Thread.activeCount() > 1) {
            Thread.yield();
        }

        System.out.println("num = " + num);
    }
}

在這裏插入圖片描述
同步代碼塊

public class ThreadDemo {
    private static int num = 0;

    public static void main(String[] args) {
        Object o = new Object();

        Thread[] threads = new Thread[20];
        for (int i = 0; i < threads.length; ++i) {
            threads[i] = new Thread() {
                @Override
                public void run() {
                    for (int i = 0; i < 10000; ++i) {
                        synchronized (o) {
                            ++num;
                        }
                    }
                }
            };
        }

        for (int i = 0; i < threads.length; ++i) {
            threads[i].start();
        }

        while (Thread.activeCount() > 1) {
            Thread.yield();
        }

        System.out.println("num = " + num);
    }
}

在這裏插入圖片描述
注意:

  • 想要使多個線程保持同步,需要保證多個線程鎖的是同一個對象
  • 使用同步代碼塊時,不建議去鎖一個Integer或String對象,因爲它們有時候不在常量池,而在堆中,就不是唯一的了,有可能多個線程鎖的是不同的對象,就無法達到同步的效果。

同步方法、靜態同步方法可以和同步代碼塊之間相互轉換
同步方法和同步代碼塊

// 同步方法
public synchronized void method() {}

// 同步代碼塊
public void method() {
	synchronized (this) {}
}

靜態同步方法和同步代碼塊

// 靜態同步方法
public static synchronized void method() {}

// 同步代碼塊
public static void method() {
	synchronized (ThreadDemo.class) {}
}

synchronized能夠保證原子性、可見性和有序性
synchronized不能鎖null,因爲synchronized鎖在對象頭上。null是沒有對象頭的

線程間通信


我們主要來看三個方法wait()notify()notifyAll()

wait方法


wait()方法就是使線程停止運行

  • 方法wait()的作用是使當前執行的線程進行等待,wait()方法是Object類的方法,該方法是用來將當前線程置入“等待隊列”並且在wait()所在的代碼處停止執行,直到接到通知或被中斷爲止
  • wait()方法只能在同步方法中或同步塊中調用。如果調用wait()時,沒有持有適當的鎖,會拋出異常
  • wait()方法執行後,當前線程釋放鎖,其他線程競爭獲取鎖

我們來看一段代碼

package Thread;

public class WaitTest {
    public static void main(String[] args) throws InterruptedException {
        Object o = new Object();
        synchronized (o) {
            System.out.println("等待中...");
            o.wait();
            System.out.println("等待結束!");
        }
        System.out.println("main方法結束!");
    }
}

在這裏插入圖片描述
這段代碼在執行到o.wait()的時候會一直等待下去。除非被中斷或喚醒

notify方法


notify()方法就是使停止的線程繼續運行

  • 方法notify()也要在同步方法或同步塊中調用,該方法是用來通知那些可能等待該對象鎖的其它線程,對其發出通知,使其可以重新競爭鎖如果有多個線程等待,則有線程規劃器隨機挑選出一個處於等待隊列的線程進行喚醒
  • notify()方法後,當前線程不會馬上釋放該對象鎖,要等到執行notify()方法的線程執行完同步代碼塊中的代碼之後纔會釋放對象鎖雖然此時已經有線程被喚醒,但是執行notify()方法的線程還持有鎖,所以被喚醒的線程依舊會等待在鎖上

我們來看一下代碼

package Thread;

public class NotifyTest {
    public static void main(String[] args) {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                synchronized (NotifyTest.class) {
                    try {
                        System.out.println("t1線程正在運行!");
                        System.out.println("t1線程正在等待!");
                        NotifyTest.class.wait();
                        System.out.println("t1線程被喚醒!");
                        System.out.println("t1線程即將退出!");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t1.start();

        Thread t2 = new Thread() {
            @Override
            public void run() {
                synchronized (NotifyTest.class) {
                    System.out.println("t2線程正在運行!");
                    NotifyTest.class.notify();
                    System.out.println("t2線程即將退出!");
                }
            }
        };
        t2.start();
    }
}

在這裏插入圖片描述

notifyAll方法


上面的notify()方法只能喚醒某一個等待線程,那麼如果有多個線程都在等待中怎麼辦呢,這個時候就可以使用notifyAll方法可以一次喚醒所有的等待線程,我們直接來看代碼

package Thread;

import com.sun.xml.internal.bind.annotation.OverrideAnnotationOf;

public class NotifyAllTest {
    public static void main(String[] args) {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                synchronized (NotifyAllTest.class) {
                    try {
                        System.out.println("t1線程正在運行!");
                        System.out.println("t1線程正在等待!");
                        NotifyAllTest.class.wait();
                        System.out.println("t1線程被喚醒!");
                        System.out.println("t1線程即將退出!");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t1.start();

        Thread t2 = new Thread() {
            @Override
            public void run() {
                synchronized (NotifyAllTest.class) {
                    try {
                        System.out.println("t2線程正在運行!");
                        System.out.println("t2線程正在等待!");
                        NotifyAllTest.class.wait();
                        System.out.println("t2線程被喚醒!");
                        System.out.println("t2線程即將退出!");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t2.start();

        Thread t3 = new Thread() {
            @Override
            public void run() {
                synchronized (NotifyAllTest.class) {
                    System.out.println("t3線程正在運行!");
                    NotifyAllTest.class.notifyAll();
                    System.out.println("t3線程即將退出!");
                }
            }
        };
        t3.start();
    }
}

在這裏插入圖片描述
線程間通信總結

  • wait()、notify()、notifyAll()三個方法的執行都必須在synchronized代碼塊中
  • 執行這三個方法必有持有相應的鎖對象
  • wait、notify、notifyAll都是java.lang.Object類的方法,而不是Thread固有的方法。換句話說,wait、notify和notifyAll這三個方法與其說是針對線程的操作,倒不如說是針對實例的等待隊列的操作。由於所有實例都有等待隊列,所以wait、notify和notifyAll也就成爲了Object類的方法。

wait和sleep的區別


  • wait之前需要請求鎖,而wait執行時會先釋放鎖,等被喚醒時再重新請求鎖。這個鎖是wait對象上的monitor lock;
  • sleep是無視鎖的存在的,即之前請求的鎖不會釋放,沒有鎖也不會請求
  • wait方法是Object的方法
  • sleep方法是Thread類的靜態方法
發佈了199 篇原創文章 · 獲贊 365 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章