Java高并发学习(六)—— LockSupport

LockSupport

理论

LockSupport是什么?

LockSupport是Java.util.concurrent.locks包下的一个类。是线程等待唤醒进制的(wait和notify)的改良版。
LockSupport是用来创建锁其他同步类基本线程阻塞原语
LockSupport的park() 和unpark()作用分别是阻塞线程(相当于Object 的wait())和解除阻塞线程(相当于Object 的notify())。
在这里插入图片描述

在学习LockSupport之前,我们知道2种让线程等待和唤醒的方法

  1. Object类的wait和notify方法实现线程的等待和唤醒
  2. Condition接口的await()方法和signal()方法实现线程的等待和唤醒。

而通过LockSupport的park() 和unpark()方法也可以让线程等待和唤醒。

在讲LockSupport之前,有必要知道为什么要引入,也就是LockSupport做到了Object和Condition的没有做到的什么地方?

wait和notify的异常情况

要求:实现线程A等待之后, B线程唤醒A线程继续工作。

package LockSup;

/**
 * @author Johnny Lin
 * @date 2021/6/13 14:25
 *
 * 
 */
public class NotifyWaitDemo {
    static Object objectLock = new Object();
    public static void main(String[] args) {

        new Thread(()->{
            synchronized (objectLock){
                System.out.println(Thread.currentThread().getName() + "\t come in");
                try {
	                System.out.println(Thread.currentThread().getName() + "\t wait……");
	                objectLock.wait();
	                System.out.println(Thread.currentThread().getName()+"\t 被唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
            synchronized (objectLock){
                System.out.println(Thread.currentThread().getName() + "\t come in");
                objectLock.notify();
                System.out.println(Thread.currentThread().getName() + "\t 通知");
            }
        },"B").start();


    }
}

正常执行结果:
在这里插入图片描述

异常情况1

可是如果将wait和notify所在代码块的synchronized关键字都注释掉,会怎么样呢?

 new Thread(()->{
                System.out.println(Thread.currentThread().getName() + "\t come in");
                try {
	                System.out.println(Thread.currentThread().getName() + "\t wait……");
	                objectLock.wait();
	                System.out.println(Thread.currentThread().getName()+"\t 被唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        },"A").start();

        new Thread(()->{
                System.out.println(Thread.currentThread().getName() + "\t come in");
                objectLock.notify();
                System.out.println(Thread.currentThread().getName() + "\t 通知");
        },"B").start();

执行结果
在这里插入图片描述

异常情况2

如果是B线程先拿到锁然后执行唤醒,后A再拿到锁进入等待状态会怎么样?

        new Thread(()->{
            //保证B线程先执行拿到锁
            try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
            synchronized (objectLock){
                System.out.println(Thread.currentThread().getName() + "\t come in");
                try {
                    System.out.println(Thread.currentThread().getName() + "\t wait……");
                    objectLock.wait();
                    System.out.println(Thread.currentThread().getName()+"\t 被唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
            synchronized (objectLock){
                System.out.println(Thread.currentThread().getName() + "\t come in");
                objectLock.notify();
                System.out.println(Thread.currentThread().getName() + "\t 通知");
            }
        },"B").start();

在这里插入图片描述

结论: Obejct类中的wait、notify、notifyAll用于线程等待和唤醒的方法,都必须在synchronized内部执行,且必须要用到关键字synchronized!!

await和signal的异常情况

package LockSup;

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

/**
 * @author Johnny Lin
 * @date 2021/6/13 14:56
 */
public class SignalAwaitDemo {

    static Lock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();
    public static void main(String[] args) {
        new Thread(()->{
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "\t come in");
            try {
                System.out.println(Thread.currentThread().getName() + "\t wait……");
                try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); }
                System.out.println(Thread.currentThread().getName()+"\t 被唤醒");
            } finally {
                lock.unlock();
            }
        },"A").start();

        //让A线程先执行
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
        new Thread(()->{
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "\t come in");
            try {
                condition.signal();
                System.out.println(Thread.currentThread().getName() + "\t 通知");
            } finally {
                lock.unlock();
            }
        },"B").start();

    }
}

执行结果
在这里插入图片描述

异常情况1

如果把lock()和unlock都注释掉会怎么样?

        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "\t come in");
            try {
                System.out.println(Thread.currentThread().getName() + "\t wait……");
                try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); }
                System.out.println(Thread.currentThread().getName()+"\t 被唤醒");
            } finally {
            }
        },"A").start();

        //让A线程先执行
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "\t come in");
            try {
                condition.signal();
                System.out.println(Thread.currentThread().getName() + "\t 通知");
            } finally {
            }
        },"B").start();

执行结果:

在这里插入图片描述

异常情况2

       new Thread(()->{
            //A线程休眠2秒 保证B线程先执行
            try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "\t come in");
            try {
                System.out.println(Thread.currentThread().getName() + "\t wait……");
                try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); }
                System.out.println(Thread.currentThread().getName()+"\t 被唤醒");
            } finally {
                lock.unlock();
            }
        },"A").start();
        new Thread(()->{
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "\t come in");
            try {
                condition.signal();
                System.out.println(Thread.currentThread().getName() + "\t 通知");
            } finally {
                lock.unlock();
            }
        },"B").start();

在这里插入图片描述

总结传统的synchronized和Lock实现等待唤醒通知的约束:

  1. 线程先要获得并持有锁,必须在锁块(synchronized或lock)中。
  2. 必须先等待后唤醒,线程才能被唤醒。

代码

在 了解了以前Object和Condition的劣处之后自然而然的就引出了LockSupport。

LockSupport类引入一个许可(permit)的概念来做到阻塞和唤醒线程的功能,每个线程都要一个permit。
permit只有两个值1和0,默认是0。permit与semaphore类似,不同的是,permit的累加上限是1 。

官方解释:
。

park()方法

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

LockSupport类

public static void park() { U.park(false, 0L); }

调用Unsafe类的park方法

@HotSpotIntrinsicCandidate
public native void park(boolean isAbsolute, long time);

unpark()方法

调用unpark(thread)方法只有,就会将thread线程的许可permit设置成1,会自动唤醒thread线程,即之前阻塞中的LockSupport.park()方法会立即返回。

注意: 多次调用unpark方法,不会累加。permit值还是1

LockSupport的unpark()方法

public static void unpark(Thread thread) {
if (thread != null)
U.unpark(thread);
}

调用Unsafe类的unpark(Object thread)

@HotSpotIntrinsicCandidate
public native void unpark(Object thread);

package LockSup;

import java.util.concurrent.locks.LockSupport;

/**
 * @author Johnny Lin
 * @date 2021/6/13 15:38
 */
public class LockSupportDemo {
    public static void main(String[] args) {

        Thread A = new Thread(()->{

            System.out.println(Thread.currentThread().getName() + "\t come in");
            System.out.println(Thread.currentThread().getName() + "\t wait……");
            //阻塞
            LockSupport.park();
            System.out.println(Thread.currentThread().getName()+"\t 被唤醒");
        },"A");

        Thread B = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t come in");
            System.out.println(Thread.currentThread().getName() + "\t 通知");
            LockSupport.unpark(A);
        }, "B");

        A.start();
        B.start();
    }
}

执行结果

在这里插入图片描述

        Thread A = new Thread(()->{
            //A 线程休眠让B线程先执行 发放通行证
            try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
                System.out.println(Thread.currentThread().getName() + "\t come in");
                System.out.println(Thread.currentThread().getName() + "\t wait……");
                //阻塞
                LockSupport.park();
                System.out.println(Thread.currentThread().getName()+"\t 被唤醒");
        },"A");

        A.start();
        
        Thread B = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t come in");
            System.out.println(Thread.currentThread().getName() + "\t 通知");
            //通知唤醒A线程
            LockSupport.unpark(A);
            LockSupport.unpark(A);
        }, "B");
        B.start();

执行结果
在这里插入图片描述
可以看到,即使是B先执行unpark(),A线程park()阻塞后也能正常被 唤醒。相当于B线程预先给A发放了通行证。

总结

LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。实际上,LockSupport调用Unsafe类中的native代码。

线程阻塞需要消耗凭证permit,这个凭证最多只有1个。当调用park方法时:

  1. 如果有凭证,则会直接消耗这个凭证然后正常退出。
  2. 如果无凭证,则必须阻塞等待凭证可用。

当调用unpark方法时,则会增加一个凭证,但凭证最多只能有1个,累加无效。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值