亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

java高并發系列 - 第14天:JUC中的LockSupport工具類,必備技能

發布時間:2020-07-27 23:07:18 來源:網絡 閱讀:286 作者:路人甲Java 欄目:編程語言

這是java高并發系列第14篇文章。

本文主要內容:

  1. 講解3種讓線程等待和喚醒的方法,每種方法配合具體的示例
  2. 介紹LockSupport主要用法
  3. 對比3種方式,了解他們之間的區別

LockSupport位于java.util.concurrent簡稱juc)包中,算是juc中一個基礎類,juc中很多地方都會使用LockSupport,非常重要,希望大家一定要掌握。

關于線程等待/喚醒的方法,前面的文章中我們已經講過2種了:

  1. 方式1:使用Object中的wait()方法讓線程等待,使用Object中的notify()方法喚醒線程
  2. 方式2:使用juc包中Condition的await()方法讓線程等待,使用signal()方法喚醒線程

這2種方式,我們先來看一下示例。

使用Object類中的方法實現線程等待和喚醒

示例1:

package com.itsoku.chat10;

import java.util.concurrent.TimeUnit;

/**
 * 微信公眾號:javacode2018,獲取年薪50萬課程
 */
public class Demo1 {

    static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被喚醒!");
            }
        });
        t1.setName("t1");
        t1.start();
        //休眠5秒
        TimeUnit.SECONDS.sleep(5);
        synchronized (lock) {
            lock.notify();
        }
    }
}

輸出:

1563592938744,t1 start!
1563592943745,t1 被喚醒!

t1線程中調用lock.wait()方法讓t1線程等待,主線程中休眠5秒之后,調用lock.notify()方法喚醒了t1線程,輸出的結果中,兩行結果相差5秒左右,程序正常退出。

示例2

我們把上面代碼中main方法內部改一下,刪除了synchronized關鍵字,看看有什么效果:

package com.itsoku.chat10;

import java.util.concurrent.TimeUnit;

/**
 * 微信公眾號:javacode2018,獲取年薪50萬課程
 */
public class Demo2 {

    static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被喚醒!");
        });
        t1.setName("t1");
        t1.start();
        //休眠5秒
        TimeUnit.SECONDS.sleep(5);
        lock.notify();
    }
}

運行結果:

Exception in thread "t1" java.lang.IllegalMonitorStateException
1563593178811,t1 start!
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Object.java:502)
    at com.itsoku.chat10.Demo2.lambda$main$0(Demo2.java:16)
    at java.lang.Thread.run(Thread.java:745)
Exception in thread "main" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at com.itsoku.chat10.Demo2.main(Demo2.java:26)

上面代碼中將synchronized去掉了,發現調用wait()方法和調用notify()方法都拋出了IllegalMonitorStateException異常,原因:Object類中的wait、notify、notifyAll用于線程等待和喚醒的方法,都必須在同步代碼中運行(必須用到關鍵字synchronized)

示例3

喚醒方法在等待方法之前執行,線程能夠被喚醒么?代碼如下:

package com.itsoku.chat10;

import java.util.concurrent.TimeUnit;

/**
 * 微信公眾號:javacode2018,獲取年薪50萬課程
 */
public class Demo3 {

    static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lock) {
                System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");
                try {
                    //休眠3秒
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被喚醒!");
            }
        });
        t1.setName("t1");
        t1.start();
        //休眠1秒之后喚醒lock對象上等待的線程
        TimeUnit.SECONDS.sleep(1);
        synchronized (lock) {
            lock.notify();
        }
        System.out.println("lock.notify()執行完畢");
    }
}

運行代碼,輸出結果:

lock.notify()執行完畢
1563593869797,t1 start!

輸出了上面2行之后,程序一直無法結束,t1線程調用wait()方法之后無法被喚醒了,從輸出中可見,notify()方法在wait()方法之前執行了,等待的線程無法被喚醒了。說明:喚醒方法在等待方法之前執行,線程無法被喚醒。

關于Object類中的用戶線程等待和喚醒的方法,總結一下:

  1. wait()/notify()/notifyAll()方法都必須放在同步代碼(必須在synchronized內部執行)中執行,需要先獲取鎖
  2. 線程喚醒的方法(notify、notifyAll)需要在等待的方法(wait)之后執行,等待中的線程才可能會被喚醒,否則無法喚醒

使用Condition實現線程的等待和喚醒

Condition的使用,前面的文章講過,對這塊不熟悉的可以移步JUC中Condition的使用,關于Condition我們準備了3個示例。

示例1

package com.itsoku.chat10;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 微信公眾號:javacode2018,獲取年薪50萬課程
 */
public class Demo4 {

    static ReentrantLock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            lock.lock();
            try {
                System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被喚醒!");
            } finally {
                lock.unlock();
            }
        });
        t1.setName("t1");
        t1.start();
        //休眠5秒
        TimeUnit.SECONDS.sleep(5);
        lock.lock();
        try {
            condition.signal();
        } finally {
            lock.unlock();
        }

    }
}

輸出:

1563594349632,t1 start!
1563594354634,t1 被喚醒!

t1線程啟動之后調用condition.await()方法將線程處于等待中,主線程休眠5秒之后調用condition.signal()方法將t1線程喚醒成功,輸出結果中2個時間戳相差5秒。

示例2

我們將上面代碼中的lock.lock()、lock.unlock()去掉,看看會發生什么。代碼:

package com.itsoku.chat10;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 微信公眾號:javacode2018,獲取年薪50萬課程
 */
public class Demo5 {

    static ReentrantLock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被喚醒!");
        });
        t1.setName("t1");
        t1.start();
        //休眠5秒
        TimeUnit.SECONDS.sleep(5);
        condition.signal();
    }
}

輸出:

Exception in thread "t1" java.lang.IllegalMonitorStateException
1563594654865,t1 start!
    at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.fullyRelease(AbstractQueuedSynchronizer.java:1723)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2036)
    at com.itsoku.chat10.Demo5.lambda$main$0(Demo5.java:19)
    at java.lang.Thread.run(Thread.java:745)
Exception in thread "main" java.lang.IllegalMonitorStateException
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
    at com.itsoku.chat10.Demo5.main(Demo5.java:29)

有異常發生,condition.await();condition.signal();都觸發了IllegalMonitorStateException異常。原因:調用condition中線程等待和喚醒的方法的前提是必須要先獲取lock的鎖

示例3

喚醒代碼在等待之前執行,線程能夠被喚醒么?代碼如下:

package com.itsoku.chat10;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 微信公眾號:javacode2018,獲取年薪50萬課程
 */
public class Demo6 {

    static ReentrantLock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.lock();
            try {
                System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被喚醒!");
            } finally {
                lock.unlock();
            }
        });
        t1.setName("t1");
        t1.start();
        //休眠5秒
        TimeUnit.SECONDS.sleep(1);
        lock.lock();
        try {
            condition.signal();
        } finally {
            lock.unlock();
        }
        System.out.println(System.currentTimeMillis() + ",condition.signal();執行完畢");
    }
}

運行結果:

1563594886532,condition.signal();執行完畢
1563594890532,t1 start!

輸出上面2行之后,程序無法結束,代碼結合輸出可以看出signal()方法在await()方法之前執行的,最終t1線程無法被喚醒,導致程序無法結束。

關于Condition中方法使用總結:

  1. 使用Condtion中的線程等待和喚醒方法之前,需要先獲取鎖。否者會報IllegalMonitorStateException異常
  2. signal()方法先于await()方法之前調用,線程無法被喚醒

Object和Condition的局限性

關于Object和Condtion中線程等待和喚醒的局限性,有以下幾點:

  1. 2中方式中的讓線程等待和喚醒的方法能夠執行的先決條件是:線程需要先獲取鎖
  2. 喚醒方法需要在等待方法之后調用,線程才能夠被喚醒

關于這2點,LockSupport都不需要,就能實現線程的等待和喚醒。下面我們來說一下LockSupport類。

LockSupport類介紹

LockSupport類可以阻塞當前線程以及喚醒指定被阻塞的線程。主要是通過park()unpark(thread)方法來實現阻塞和喚醒線程的操作的。

每個線程都有一個許可(permit),permit只有兩個值1和0,默認是0。

  1. 當調用unpark(thread)方法,就會將thread線程的許可permit設置成1(注意多次調用unpark方法,不會累加,permit值還是1)。
  2. 當調用park()方法,如果當前線程的permit是1,那么將permit設置為0,并立即返回。如果當前線程的permit是0,那么當前線程就會阻塞,直到別的線程將當前線程的permit設置為1時,park方法會被喚醒,然后會將permit再次設置為0,并返回。

注意:因為permit默認是0,所以一開始調用park()方法,線程必定會被阻塞。調用unpark(thread)方法后,會自動喚醒thread線程,即park方法立即返回。

LockSupport中常用的方法

阻塞線程

  • void park():阻塞當前線程,如果調用unpark方法或者當前線程被中斷,從能從park()方法中返回

  • void park(Object blocker):功能同方法1,入參增加一個Object對象,用來記錄導致線程阻塞的阻塞對象,方便進行問題排查

  • void parkNanos(long nanos):阻塞當前線程,最長不超過nanos納秒,增加了超時返回的特性

  • void parkNanos(Object blocker, long nanos):功能同方法3,入參增加一個Object對象,用來記錄導致線程阻塞的阻塞對象,方便進行問題排查

  • void parkUntil(long deadline):阻塞當前線程,直到deadline,deadline是一個絕對時間,表示某個時間的毫秒格式

  • void parkUntil(Object blocker, long deadline):功能同方法5,入參增加一個Object對象,用來記錄導致線程阻塞的阻塞對象,方便進行問題排查;

喚醒線程

  • void unpark(Thread thread):喚醒處于阻塞狀態的指定線程

示例1

主線程線程等待5秒之后,喚醒t1線程,代碼如下:

package com.itsoku.chat10;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 微信公眾號:javacode2018,獲取年薪50萬課程
 */
public class Demo7 {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");
            LockSupport.park();
            System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被喚醒!");
        });
        t1.setName("t1");
        t1.start();
        //休眠5秒
        TimeUnit.SECONDS.sleep(5);
        LockSupport.unpark(t1);
        System.out.println(System.currentTimeMillis() + ",LockSupport.unpark();執行完畢");
    }
}

輸出:

1563597664321,t1 start!
1563597669323,LockSupport.unpark();執行完畢
1563597669323,t1 被喚醒!

t1中調用LockSupport.park();讓當前線程t1等待,主線程休眠了5秒之后,調用LockSupport.unpark(t1);將t1線程喚醒,輸出結果中1、3行結果相差5秒左右,說明t1線程等待5秒之后,被喚醒了。

LockSupport.park();無參數,內部直接會讓當前線程處于等待中;unpark方法傳遞了一個線程對象作為參數,表示將對應的線程喚醒。

示例2

喚醒方法放在等待方法之前執行,看一下線程是否能夠被喚醒呢?代碼如下:

package com.itsoku.chat10;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

/**
 * 微信公眾號:javacode2018,獲取年薪50萬課程
 */
public class Demo8 {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");
            LockSupport.park();
            System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被喚醒!");
        });
        t1.setName("t1");
        t1.start();
        //休眠1秒
        TimeUnit.SECONDS.sleep(1);
        LockSupport.unpark(t1);
        System.out.println(System.currentTimeMillis() + ",LockSupport.unpark();執行完畢");
    }
}

輸出:

1563597994295,LockSupport.unpark();執行完畢
1563597998296,t1 start!
1563597998296,t1 被喚醒!

代碼中啟動t1線程,t1線程內部休眠了5秒,然后主線程休眠1秒之后,調用了LockSupport.unpark(t1);喚醒線程t1,此時LockSupport.park();方法還未執行,說明喚醒方法在等待方法之前執行的;輸出結果中2、3行結果時間一樣,表示LockSupport.park();沒有阻塞了,是立即返回的。

說明:喚醒方法在等待方法之前執行,線程也能夠被喚醒,這點是另外2中方法無法做到的。Object和Condition中的喚醒必須在等待之后調用,線程才能被喚醒。而LockSupport中,喚醒的方法不管是在等待之前還是在等待之后調用,線程都能夠被喚醒。

示例3

park()讓線程等待之后,是否能夠響應線程中斷?代碼如下:

package com.itsoku.chat10;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

/**
 * 微信公眾號:javacode2018,獲取年薪50萬課程
 */
public class Demo9 {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");
            System.out.println(Thread.currentThread().getName() + ",park()之前中斷標志:" + Thread.currentThread().isInterrupted());
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + ",park()之后中斷標志:" + Thread.currentThread().isInterrupted());
            System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被喚醒!");
        });
        t1.setName("t1");
        t1.start();
        //休眠5秒
        TimeUnit.SECONDS.sleep(5);
        t1.interrupt();

    }
}

輸出:

1563598536736,t1 start!
t1,park()之前中斷標志:false
t1,park()之后中斷標志:true
1563598541736,t1 被喚醒!

t1線程中調用了park()方法讓線程等待,主線程休眠了5秒之后,調用t1.interrupt();給線程t1發送中斷信號,然后線程t1從等待中被喚醒了,輸出結果中的1、4行結果相差5秒左右,剛好是主線程休眠了5秒之后將t1喚醒了。結論:park方法可以相應線程中斷。

LockSupport.park方法讓線程等待之后,喚醒方式有2種:

  1. 調用LockSupport.unpark方法
  2. 調用等待線程的interrupt()方法,給等待的線程發送中斷信號,可以喚醒線程

示例4

LockSupport有幾個阻塞放有一個blocker參數,這個參數什么意思,上一個實例代碼,大家一看就懂了:

package com.itsoku.chat10;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

/**
 * 微信公眾號:javacode2018,獲取年薪50萬課程
 */
public class Demo10 {

    static class BlockerDemo {
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            LockSupport.park();
        });
        t1.setName("t1");
        t1.start();

        Thread t2 = new Thread(() -> {
            LockSupport.park(new BlockerDemo());
        });
        t2.setName("t2");
        t2.start();
    }
}

運行上面代碼,然后用jstack查看一下線程的堆棧信息:

"t2" #13 prio=5 os_prio=0 tid=0x00000000293ea800 nid=0x91e0 waiting on condition [0x0000000029c3f000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000007180bfeb0> (a com.itsoku.chat10.Demo10$BlockerDemo)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at com.itsoku.chat10.Demo10.lambda$main$1(Demo10.java:22)
        at com.itsoku.chat10.Demo10$$Lambda$2/824909230.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)

"t1" #12 prio=5 os_prio=0 tid=0x00000000293ea000 nid=0x9d4 waiting on condition [0x0000000029b3f000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)
        at com.itsoku.chat10.Demo10.lambda$main$0(Demo10.java:16)
        at com.itsoku.chat10.Demo10$$Lambda$1/1389133897.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)

代碼中,線程t1和t2的不同點是,t2中調用park方法傳入了一個BlockerDemo對象,從上面的線程堆棧信息中,發現t2線程的堆棧信息中多了一行- parking to wait for &lt;0x00000007180bfeb0&gt; (a com.itsoku.chat10.Demo10$BlockerDemo),剛好是傳入的BlockerDemo對象,park傳入的這個參數可以讓我們在線程堆棧信息中方便排查問題,其他暫無他用。

LockSupport的其他等待方法,包含有超時時間了,過了超時時間,等待方法會自動返回,讓線程繼續運行,這些方法在此就不提供示例了,有興趣的朋友可以自己動動手,練一練。

線程等待和喚醒的3種方式做個對比

到目前為止,已經說了3種讓線程等待和喚醒的方法了

  1. 方式1:Object中的wait、notify、notifyAll方法
  2. 方式2:juc中Condition接口提供的await、signal、signalAll方法
  3. 方式3:juc中的LockSupport提供的park、unpark方法

3種方式對比:

Object Condtion LockSupport
前置條件 需要在synchronized中運行 需要先獲取Lock的鎖
無限等待 支持 支持 支持
超時等待 支持 支持 支持
等待到將來某個時間返回 不支持 支持 支持
等待狀態中釋放鎖 會釋放 會釋放 不會釋放
喚醒方法先于等待方法執行,能否喚醒線程 可以
是否能響應線程中斷
線程中斷是否會清除中斷標志
是否支持等待狀態中不響應中斷 不支持 支持 不支持

java高并發系列連載中,總計估計會有四五十篇文章,可以關注公眾號:javacode2018,獲取最新文章。

java高并發系列 - 第14天:JUC中的LockSupport工具類,必備技能

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

玛曲县| 翼城县| 新河县| 白银市| 余姚市| 赣榆县| 怀来县| 巴楚县| 新平| 沁水县| 河西区| 广东省| 登封市| 日照市| 祁连县| 鄂尔多斯市| 象山县| 岳阳市| 加查县| 平谷区| 怀安县| 衡东县| 板桥市| 饶河县| 大田县| 鹤岗市| 铁岭市| 元江| 富川| 白银市| 盐池县| 西乌| 江永县| 凤凰县| 辽阳县| 杭锦后旗| 山东| 蕉岭县| 丹东市| 兴文县| 师宗县|