Java併發編程之線程屬性和方法

1. 線程方法

1.1 方法概覽

類.方法 簡介
Thread.sleep() 線程休眠,不釋放鎖
Thread.join() 等待其它線程執行完畢
Thread.yield() 放棄已經獲取到的CPU資源
Thread.currentThread() 獲取當前線程的引用
Thread.start()/Thread.run() 啓動線程和線程執行內容
Thread.interrupt() 中斷線程
Thread.stop()/Thread.suspend()/Thread.resume() 已廢棄
Object.wait()/Object.notify()/Object.notifyAll() 線程等待和喚醒

1.2 wait()/notify()/notifyAll()

  • 阻塞階段
    執行了wait方法意味着當前線程進入阻塞階段,直到發生以下四種情況之一,該線程纔會被喚醒:
    1. 另外一個線程調用這個對象的notify方法且剛好被喚醒的是本線程;
    2. 另外一個線程調用這個對象的notifyAll方法;
    3. 過了wait規定的超時時間,如果傳入0,就是永久等待;
    4. 線程自身調用了interrupt方法,wait方法因爲響應中斷被喚醒,進而拋出異常;
  • 喚醒階段
    1. notify方法會喚醒單個等待線程,如果有多個等待線程,則隨機選取一個;
    2. notifyAll方法會喚醒所有等待線程;

1.3 wait()/notify()/notifyAll()方法詳解

下面展示wait和notify的基本用法:

public class WaitNotify {

    private static Object object = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object) {
                    System.out.println(Thread.currentThread().getName() + "開始執行");
                    try {
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "重新獲取到鎖");
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object) {
                    object.notify();
                    System.out.println(Thread.currentThread().getName() + "調用了notify()");
                }
            }
        });
        t1.start();
        Thread.sleep(200);
        t2.start();
    }
}
Thread-0開始執行
Thread-1調用了notify()
Thread-0重新獲取到鎖

可以看出,t1線程先啓動並執行了wait方法,釋放了鎖掛起,然後t2線程啓動,獲取到了鎖並執行了notify重新喚醒了t1線程,t1重新獲取到鎖,在掛起的位置繼續執行。
下面是wait和notifyAll的代碼演示:

public class WaitNotifyAll implements Runnable {

    private static Object object = new Object();

    @Override
    public void run() {
        synchronized (object) {
            System.out.println(Thread.currentThread().getName() + "獲取到鎖");
            try {
                object.wait();
                System.out.println(Thread.currentThread().getName() + "重新獲取到鎖");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        WaitNotifyAll waitNotifyAll = new WaitNotifyAll();
        Thread t1 = new Thread(waitNotifyAll);
        Thread t2 = new Thread(waitNotifyAll);
        t1.start();
        t2.start();
        Thread.sleep(200);
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object) {
                    object.notifyAll();
//                    object.notify();
                    System.out.println(Thread.currentThread().getName() + "拿到了鎖並執行了notifyAll");
                }
            }
        });
        t3.start();
    }
}
Thread-0獲取到鎖
Thread-1獲取到鎖
Thread-2拿到了鎖並執行了notifyAll
Thread-1重新獲取到鎖
Thread-0重新獲取到鎖

首先t1拿到了鎖並執行了wait方法掛起t1線程,然後t2拿到了鎖並執行了wait方法掛起t2線程,然後t3拿到了鎖並執行了notifyAll喚醒t1和t2線程,t2線程先重新獲取到了鎖,待執行完畢之後釋放了鎖,t2重新獲取到了鎖。
如果改用notify方法則隨機喚醒t1和t2線程其中的一個,如果t1被喚醒,則t2陷入了無盡的等待,整個進程永遠不會結束。
下面展示wait方法只能釋放調用wait方法的對象的鎖,不能釋放其他對象的鎖:

public class ReleaseOwnMonitor {

    private static final Object lockA = new Object();
    private static final Object lockB = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lockA) {
                    System.out.println(Thread.currentThread().getName() + "獲取到lockA");
                    synchronized (lockB) {
                        System.out.println(Thread.currentThread().getName() + "獲取到lockB");
                        try {
                            System.out.println(Thread.currentThread().getName() + "釋放了lockA");
                            lockA.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lockA) {
                    System.out.println(Thread.currentThread().getName() + "獲取到lockA");
                    System.out.println(Thread.currentThread().getName() + "正在嘗試獲取lockB...");
                    synchronized (lockB) {
                        //看看t2能不能獲取到lockB
                        System.out.println(Thread.currentThread().getName() + "獲取到lockB");
                        try {
                            lockA.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
        t1.start();
        Thread.sleep(500);
        t2.start();
    }
}
Thread-0獲取到lockA
Thread-0獲取到lockB
Thread-0釋放了lockA
Thread-1獲取到lockA
Thread-1正在嘗試獲取lockB...

結果表明,t2一直在嘗試獲取lockB,但是由於在t1中只有lockA執行了wait方法,釋放的也只是lockA的鎖,t1仍然持有lockB的鎖,所以t2獲取不到lockB的鎖。

1.4 wait()/notify()/notifyAll()特點和性質

  1. 用這些方法之前,當前線程必須首先獲取到monitor鎖
  2. 如果使用notify方法只會喚醒一個等待線程
  3. 都屬於Object類,所有的類繼承與Object,所有的對象都可以調用這些方法
  4. 類似功能的Condition
  5. 線程同時持有多個鎖,使用wait方法只釋放調用wait方法的對象的鎖
  6. 線程執行object.wait後,進入到WAITING狀態,而線程被喚醒後,因爲是另外一個線程持有相同的鎖才能執行object.notify方法進行喚醒,另外一個線程可能還沒有執行完,所以當前線程通常沒有立即獲取到monitor鎖,那麼當前線程會從WAIT狀態進入到BLOCKED狀態,搶到鎖後會進入到RUNNABLE狀態
  7. 發生異常,可以直接到TERMINATED狀態

1.5 sleep()方法詳解

sleep可以讓線程進入Waiting狀態,並且不佔用CPU資源,但是不釋放鎖,直到規定時間後再執行,休眠期間如果被中斷,會拋異常並清除中斷狀態。

public class SleepDontReleaseMonitor implements Runnable {

    @Override
    public void run() {
        syn();
    }

    private synchronized void syn() {
        System.out.println(Thread.currentThread().getName() + "獲取鎖");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "退出同步代碼塊");
    }

    public static void main(String[] args) {
        SleepDontReleaseMonitor sleepDontReleaseMonitor = new SleepDontReleaseMonitor();
        new Thread(sleepDontReleaseMonitor).start();
        new Thread(sleepDontReleaseMonitor).start();
    }

}
public class SleepDontReleaseLock implements Runnable {

    private static final Lock lock = new ReentrantLock();
    @Override
    public void run() {
        lock.lock();
        System.out.println(Thread.currentThread().getName() + "獲取到了鎖");
        try {
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName() + "甦醒");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        SleepDontReleaseLock sleepDontReleaseLock = new SleepDontReleaseLock();
        new Thread(sleepDontReleaseLock).start();
        new Thread(sleepDontReleaseLock).start();
    }
}

可以看出,無論是synchronized和lock,執行sleep方法都不釋放鎖。

1.6 join()方法詳解

  • 作用:因爲新的線程加入了我們,我們要等他執行完再出發
  • 用法:main等待t1執行完畢,如下所示:
public class JoinBasic implements Runnable {

    @Override
    public void run() {
        System.out.println("子線程開始執行");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("子線程執行結束");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new JoinBasic());
        thread.start();
        thread.join();
        System.out.println("主線程執行結束");
    }
}

子線程開始執行
子線程執行結束
主線程執行結束
  • join執行期間,是主線程等待子線程,所以我們看看join期間主線程是什麼狀態:
public class JoinMainState implements Runnable {
    Thread mainThread = Thread.currentThread();
    @Override
    public void run() {
        System.out.println("子線程開始執行");
        try {
            Thread.sleep(1000);
            System.out.println(mainThread.getState());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("子線程執行結束");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new JoinMainState());
        t1.start();
        t1.join();
    }
}
子線程開始執行
WAITING
子線程執行結束

結果表明,主線程等待子線程join期間,主線程是WAITING狀態

  • 因爲線程執行完畢之後會自動調用notifyAll方法,所以我們可以讓主線程等待,然後等子線程執行完後自動喚醒主線程,所以替代方法如下:
public class JoinPrinciple {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "子線程執行完畢");
            }
        });
        thread.start();
        System.out.println("等待所有子線程運行完畢");
//        thread.join();
        //給thread對象上鎖,thread對象執行完run方法後會自動調用thread.notifyAll()方法喚醒其他線程,即喚醒主線程
        synchronized (thread) {
            thread.wait();
        }
        System.out.println("所有子線程運行完畢");
    }
}
等待所有子線程運行完畢
Thread-0子線程執行完畢
所有子線程運行完畢
        synchronized (thread) {
            thread.wait();
        }

上面這段代碼也是join方法中的核心方法。

1.7 yield方法詳解

  • 作用:釋放我的CPU時間片,線程狀態仍然是Runnable狀態,不會釋放鎖,不會阻塞,即便釋放了CPU時間片,下一個可能還是我
  • yield和sleep區別:sleep期間,線程調度器不會把這個線程調度起來,而yield期間,立刻又可以被調度起來。

2. 線程屬性

2.1 屬性概覽

屬性名稱 用途
ID 標識不同的線程
Name 自定義線程名稱
isDaemon 是否是守護線程
Priority 告訴線程調度器,用戶希望哪些線程多運行、哪些少運行,共10個等級,默認值是5

2.2 ID

從1開始,我們自己創建線程的ID早已不是2,因爲JVM在後開啓了很多其它子線程。

public class threadId {
    public static void main(String[] args) {
        Thread thread = new Thread();
        System.out.println("子線程:" + thread.getId());
        System.out.println("主線程:" + Thread.currentThread().getId());
    }
}
子線程:11
主線程:1

2.3 Name

對於沒有指定名字的線程,會有默認名稱

public Thread() {
  init(null, null, "Thread-" + nextThreadNum(), 0);
}
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
  return threadInitNumber++;
}

2.2 isDaemon

給用戶線程提供服務,沒有用戶線程就沒有守護線程

  1. 線程類型默認繼承自父線程,守護線程創建的線程自動是守護線程,用戶線程同理

  2. 被誰啓動,通常,所有守護線程由JVM啓動,JVM啓動時,會有一個用戶線程,main函數

  3. 不影響JVM退出,所有用戶線程都結束,即便有守護線程,JVM也會退出

整體無區別,唯一的區別在於是否影響JVM的退出,守護線程不會影響。

2.2 Priority

10個級別,默認5

    /**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;

程序設計不應依賴於優先級,跟操作系統息息相關,不可靠

  • 因爲不同操作系統不一樣
  • 優先級會被操作系統改變
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章