您当前的位置: 首页 > 

Dongguo丶

暂无认证

  • 2浏览

    0关注

    472博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

LockSupport与线程等待唤醒机制

Dongguo丶 发布时间:2021-09-21 07:59:51 ,浏览量:2

LockSupport是什么

LockSupport线程工具类 定义了许多方法来控制当前线程 俗称 锁中断

是用来创建锁和其他同步类的基本线程阻塞原语。

LockSupport中的park()和unpark()分别是阻塞线程和解除阻塞线程

LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能, 每个线程都有一个许可, 许可只有两个值1和零,默认是零。 可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1。

线程等待唤醒机制 3种让线程等待和唤醒的方法

方式1:使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程

Object wait notify notifyAll

方式2:使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程

Condition await signal signalAll

方式3:LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程

LockSupport park unpark

Object类中的wait和notify方法实现线程等待和唤醒
package com.dongguo.locksupport;

/**
 * @author Dongguo
 * @date 2021/9/6 0006-15:40
 * @description:
 */
public class LockSupportDemo {
    public static void main(String[] args) {
        Object objectLock = new Object();
        new Thread(()->{
            synchronized (objectLock){
                try {
                    System.out.println(Thread.currentThread().getName()+"--阻塞");
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"--被唤醒");
            }
        },"t1").start();
        new Thread(()->{
            synchronized (objectLock){
                objectLock.notify();
                System.out.println(Thread.currentThread().getName()+"--发出唤醒通知");
            }
        },"t2").start();
    }
}
运行结果:
t1--阻塞
t2--发出唤醒通知
t1--被唤醒
异常一

Object类中的wait和notify方法只能在synchronized同步代码块或者同步方法中使用,且成对出现。不然会报异常IllegalMonitorStateException

package com.dongguo.locksupport;

/**
 * @author Dongguo
 * @date 2021/9/6 0006-15:40
 * @description:
 */
public class LockSupportDemo {
    public static void main(String[] args) {
        Object objectLock = new Object();
        new Thread(()->{
//            synchronized (objectLock){
                try {
                    System.out.println(Thread.currentThread().getName()+"--阻塞");
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"--被唤醒");
//            }
        },"t1").start();
        new Thread(()->{
//            synchronized (objectLock){
                objectLock.notify();
                System.out.println(Thread.currentThread().getName()+"--发出唤醒通知");
//            }
        },"t2").start();
    }
}
运行结果
t1--阻塞
Exception in thread "t1" Exception in thread "t2" java.lang.IllegalMonitorStateException
	at java.lang.Object.notify(Native Method)
	at com.dongguo.locksupport.LockSupportDemo.lambda$main$1(LockSupportDemo.java:24)
	at java.lang.Thread.run(Thread.java:748)
java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at com.dongguo.locksupport.LockSupportDemo.lambda$main$0(LockSupportDemo.java:15)
	at java.lang.Thread.run(Thread.java:748)
异常二

wait()要在notify()之前,

package com.dongguo.locksupport;

import java.util.concurrent.TimeUnit;

/**
 * @author Dongguo
 * @date 2021/9/6 0006-15:40
 * @description:
 */
public class LockSupportDemo {
    public static void main(String[] args) {
        Object objectLock = new Object();
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (objectLock){
                try {
                    System.out.println(Thread.currentThread().getName()+"--阻塞");
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"--被唤醒");
            }
        },"t1").start();
        new Thread(()->{
            synchronized (objectLock){
                objectLock.notify();
                System.out.println(Thread.currentThread().getName()+"--发出唤醒通知");
            }
        },"t2").start();
    }
}

运行结果

image-20210906155147340

t1阻塞等待被唤醒

Condition接口中的await后signal方法实现线程的等待和唤醒
package com.dongguo.locksupport;

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

/**
 * @author Dongguo
 * @date 2021/9/6 0006-15:40
 * @description:
 */
public class LockSupportDemo {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        new Thread(() -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "--阻塞");
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
            System.out.println(Thread.currentThread().getName() + "--被唤醒");
        }, "t1").start();
        new Thread(() -> {
            lock.lock();
            try {
                condition.signal();
                System.out.println(Thread.currentThread().getName() + "--发出唤醒通知");
            } finally {
                lock.unlock();
            }
        }, "t2").start();
    }
}
运行结果
t1--阻塞
t2--发出唤醒通知
t1--被唤醒
异常一

lock、unlock里面才能正确调用condition中线程等待await和唤醒signal的方法

package com.dongguo.locksupport;

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

/**
 * @author Dongguo
 * @date 2021/9/6 0006-15:40
 * @description:
 */
public class LockSupportDemo {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        new Thread(() -> {
//            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "--阻塞");
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
//                lock.unlock();
            }
            System.out.println(Thread.currentThread().getName() + "--被唤醒");
        }, "t1").start();
        new Thread(() -> {
//            lock.lock();
            try {
                condition.signal();
                System.out.println(Thread.currentThread().getName() + "--发出唤醒通知");
            } finally {
//                lock.unlock();
            }
        }, "t2").start();
    }
}
运行结果
t1--阻塞
Exception in thread "t1" Exception in thread "t2" java.lang.IllegalMonitorStateException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
	at com.dongguo.locksupport.LockSupportDemo.lambda$main$1(LockSupportDemo.java:33)
	at java.lang.Thread.run(Thread.java:748)
java.lang.IllegalMonitorStateException
	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.dongguo.locksupport.LockSupportDemo.lambda$main$0(LockSupportDemo.java:22)
	at java.lang.Thread.run(Thread.java:748)
异常二

先await后signal顺序不能换

package com.dongguo.locksupport;

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

/**
 * @author Dongguo
 * @date 2021/9/6 0006-15:40
 * @description:
 */
public class LockSupportDemo {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "--阻塞");
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
            System.out.println(Thread.currentThread().getName() + "--被唤醒");
        }, "t1").start();
        new Thread(() -> {
            lock.lock();
            try {
                condition.signal();
                System.out.println(Thread.currentThread().getName() + "--发出唤醒通知");
            } finally {
                lock.unlock();
            }
        }, "t2").start();
    }
}

运行结果

image-20210906160450574

t1阻塞

Object和Condition使用的限制条件

线程先要获得并持有锁,必须在锁块(synchronized或lock)中,并且wait/notify或await/signal成对出现

必须要先等待后唤醒,线程才能够被唤醒

加锁会被阻塞

LockSupport类中的park等待和unpark唤醒 主要方法 阻塞

park() /park(Object blocker)

阻塞当前线程/阻塞传入的具体线程

一般使用park()

调用LockSupport.park()时

park()

    /**
     * 为了线程调度,在许可可用之前阻塞当前线程。 
     * 如果许可可用,则使用该许可,并且该调用立即返回;
     * 否则,为线程调度禁用当前线程,并在发生以下三种情况之一以前,使其处于休眠状态:
     *  1. 其他某个线程将当前线程作为目标调用 unpark
     *  2. 其他某个线程中断当前线程
     *  3. 该调用不合逻辑地(即毫无理由地)返回
     */
public static void park() {
    UNSAFE.park(false, 0L);
}

park(Object blocker)

public static void park(Object blocker) {
   //获取当前线程
    Thread t = Thread.currentThread();
   //记录当前线程阻塞的原因,底层就是unsafe.putObject,就是把对象存储起来
    setBlocker(t, blocker);
    //执行park
    unsafe.park(false, 0L);
   //线程恢复后,去掉阻塞原因
    setBlocker(t, null);
}

permit默认是零,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park方法会被唤醒, 然后会将permit再次设置为零并返回。

唤醒

unpark(Thread thread)

唤醒处于阻塞状态的指定线程

LockSupport.unpark(thread);

  /**
     * 如果给定线程的许可尚不可用,则使其可用。
     * 如果线程在 park 上受阻塞,则它将解除其阻塞状态。
     * 否则,保证下一次调用 park 不会受阻塞。
     * 如果给定线程尚未启动,则无法保证此操作有任何效果。 
     * @param thread: 要执行 unpark 操作的线程;该参数为 null 表示此操作没有任何效果。
     */
public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}

调用unpark(thread)方法后,就会将thread线程的许可permit设置成1(注意多次调用unpark方法,不会累加,permit值还是1)会自动唤醒thread线程,即之前阻塞中的LockSupport.park()方法会立即返回。

从源码可以看到真正的实现均在 Unsafe.class中

public native void unpark(Object var1);

public native void park(boolean var1, long var2);
与 Object 的 wait & notify (condition的await signal)相比

wait,notify 和 notifyAll 必须配合 Object Monitor 一起使用,而 park,unpark 不必 park & unpark 是以线程为单位来【阻塞】和【唤醒】线程,而 notify 只能随机唤醒一个等待线程,notifyAll 是唤醒所有等待线程,就不那么【精确】 park & unpark 可以先 unpark,而 wait & notify 不能先 notify

代码
package com.dongguo.locksupport;

import java.util.concurrent.locks.LockSupport;

/**
 * @author Dongguo
 * @date 2021/9/6 0006-16:14
 * @description:
 */
public class LockSupportDemo1 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "--阻塞");
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "--被唤醒");
        }, "t1");
        t1.start();
        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "--发出唤醒通知");
            LockSupport.unpark(t1);
        },"t2").start();
    }
}
运行结果
t1--阻塞
t2--发出唤醒通知
t1--被唤醒

LockSupport解决了Object和Condition实现线程阻塞和唤醒

线程先要获得并持有锁,必须在锁块(synchronized或lock)中的问题

而且park和unpark顺序可以不固定

package com.dongguo.locksupport;

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

/**
 * @author Dongguo
 * @date 2021/9/6 0006-16:14
 * @description:
 */
public class LockSupportDemo1 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "--阻塞");
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "--被唤醒");
        }, "t1");
        t1.start();
        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "--发出唤醒通知");
            LockSupport.unpark(t1);
        },"t2").start();
    }
}
运行结果
t2--发出唤醒通知
t1--阻塞
t1--被唤醒

有一点比较难理解的,是unpark操作可以再park操作之前。也就是说,先提供许可。当某线程调用park时,已经有许可了,它就消费这个许可,然后可以继续运行。这其实是必须的。考虑最简单的生产者(Producer)消费者(Consumer)模型:Consumer需要消费一个资源,于是调用park操作等待;Producer则生产资源,然后调用unpark给予Consumer使用的许可。非常有可能的一种情况是,Producer先生产,这时候Consumer可能还没有构造好(比如线程还没启动,或者还没切换到该线程)。那么等Consumer准备好要消费时,显然这时候资源已经生产好了,可以直接用,那么park操作当然可以直接运行下去。如果没有这个语义,那将非常难以操作。

但是这个“许可”是不能叠加的,“许可”是一次性的。

比如线程t2连续调用了三次unpark函数,当线程t1调用park函数就使用掉这个“许可”,如果线程t1再次调用park,则进入等待状态。

先执行两个park()后,再unpark()

package com.dongguo.locksupport;

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

/**
 * @author Dongguo
 * @date 2021/9/6 0006-16:14
 * @description:
 */
public class LockSupportDemo1 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {

            System.out.println(Thread.currentThread().getName() + "--阻塞");
            LockSupport.park();
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "--被唤醒");
        }, "t1");
        t1.start();
        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "--发出唤醒通知");
            LockSupport.unpark(t1);
            LockSupport.unpark(t1);
            LockSupport.unpark(t1);
        },"t2").start();
    }
}

运行结果

image-20210906163308869

解决使用两个线程,两次唤醒

package com.dongguo.locksupport;

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

/**
 * @author Dongguo
 * @date 2021/9/6 0006-16:14
 * @description:
 */
public class LockSupportDemo1 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {

            System.out.println(Thread.currentThread().getName() + "--阻塞");
            LockSupport.park();
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "--被唤醒");
        }, "t1");
        t1.start();

        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "--t2发出唤醒通知");
            LockSupport.unpark(t1);
        },"t2").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "--t3发出唤醒通知");
            LockSupport.unpark(t1);
        },"t3").start();
    }
}
运行结果
t1--阻塞
t2--t2发出唤醒通知
t3--t3发出唤醒通知
t1--被唤醒

要确保unpark不是唤醒同一个park

先执行两个unpark()后,再park()

package com.dongguo.locksupport;

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

/**
 * @author Dongguo
 * @date 2021/9/6 0006-16:14
 * @description:
 */
public class LockSupportDemo1 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "--阻塞");
            LockSupport.park();
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "--被唤醒");
        }, "t1");
        t1.start();
        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "--发出唤醒通知");
            LockSupport.unpark(t1);
            LockSupport.unpark(t1);
        },"t2").start();
    }
}

运行结果

image-20210906162810899

先执行t2,执行两次unpark 将许可设置为1

LockSupport.unpark(t1); LockSupport.unpark(t1);

执行t1

第一次执行park LockSupport.park(); 消费许可

第二次执行park 许可为0,进入阻塞

LockSupport对应中断的响应性

AQS (AbstractQueuedSynchronizer)底层就是调用park()方法,保证阻塞被唤醒后,如果加锁失败,可以再次阻塞,减轻资源消耗,在出现等待队列时,队列中的某个节点是可以响应中断的:

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

线程如果因为调用park而阻塞的话,能够响应中断请求(中断状态被设置成true),但是不会抛出InterruptedException。

package com.dongguo.locksupport;

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

/**
 * @author Dongguo
 * @date 2021/9/6 0006-20:49
 * @description:
 */
public class LockSupportDemo2 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println( "park前中断标志位1:" + Thread.currentThread().isInterrupted());
            LockSupport.park();
            System.out.println(Thread.currentThread().getName()+"被唤醒 中断状态2"+ Thread.currentThread().isInterrupted());
        }, "t1");
        t1.start();

        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.interrupt();
    }
}
运行结果
park前中断标志位1:false
t1被唤醒 中断状态2true

park()的线程被interrupt()后的状态和正常线程被interrupt()后的状态一样

park方法可以响应中断

所以park方法让线程等待后 有两种方法唤醒

1unpark

2interrupt

但是推荐unpark

如果打断标记已经是 true, 则 park 会失效

park()的线程被interrupt()后

再次park()就失效了 无法阻塞

package com.dongguo.locksupport;

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

/**
 * @author Dongguo
 * @date 2021/9/6 0006-20:49
 * @description:
 */
public class LockSupportDemo2 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println( "park前中断标志位1:" + Thread.currentThread().isInterrupted());
            LockSupport.park();
            System.out.println(Thread.currentThread().getName()+"被唤醒 中断状态2"+ Thread.currentThread().isInterrupted());
        }, "t1");
        t1.start();

        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.interrupt();
        LockSupport.unpark(t1);
    }
}  
运行结果
park前中断标志位1:false
t1被唤醒 中断状态2true
park前中断标志位1:true
t1被唤醒 中断状态2true
park前中断标志位1:true
t1被唤醒 中断状态2true
park前中断标志位1:true
t1被唤醒 中断状态2true
park前中断标志位1:true
t1被唤醒 中断状态2true

for循环第一次park阻塞后被interrupt()唤醒

之后的循环park都无法将线程阻塞了。

可以使用 Thread.interrupted() 清除打断状态

package com.dongguo.locksupport;

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

/**
 * @author Dongguo
 * @date 2021/9/6 0006-20:49
 * @description:
 */
public class LockSupportDemo2 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i             
关注
打赏
1638062488
查看更多评论
0.2681s