菜鸟笔记
提升您的技术认知

locksupport从入门到深入理解-ag真人游戏

1、为什么locksupport也是核心基础类? aqs框架借助于两个类:unsafe(提供cas操作)和locksupport(提供park/unpark操作) 2、写出分别通过wait/notify和locksupport的park/unpark实现同步?
3、locksupport.park()会释放锁资源吗? 那么condition.await()呢?
4、thread.sleep()、object.wait()、condition.await()、locksupport.park()的区别?
5、 重点 如果在wait()之前执行了notify()会怎样?
6、如果在park()之前执行了unpark()会怎样?

locksupport是用来创建锁和其他同步工具类的基本线程阻塞原语。
java锁和同步器框架的核心 aqs: abstractqueuedsynchronizer,就是通过调用 locksupport .park()和 locksupport .unpark()实现线程的阻塞和唤醒 的。 locksupport 很类似于二元信号量(只有1个许可证可供使用),如果这个许可还没有被占用,当前线程获取许可并继 续 执行;如果许可已经被占用,当前线 程阻塞,等待获取许可。

locksupport 类的属性

public class locksupport {
  
    // hotspot implementation via intrinsics api
    private static final sun.misc.unsafe unsafe;
    // 表示内存偏移地址
    private static final long parkblockeroffset;
    // 表示内存偏移地址
    private static final long seed;
    // 表示内存偏移地址
    private static final long probe;
    // 表示内存偏移地址
    private static final long secondary;
    
    static {
  
        try {
  
            // 获取unsafe实例
            unsafe = sun.misc.unsafe.getunsafe();
            // 线程类类型
            class tk = thread.class;
            // 获取thread的parkblocker字段的内存偏移地址
            parkblockeroffset = unsafe.objectfieldoffset
                (tk.getdeclaredfield("parkblocker"));
            // 获取thread的threadlocalrandomseed字段的内存偏移地址
            seed = unsafe.objectfieldoffset
                (tk.getdeclaredfield("threadlocalrandomseed"));
            // 获取thread的threadlocalrandomprobe字段的内存偏移地址
            probe = unsafe.objectfieldoffset
                (tk.getdeclaredfield("threadlocalrandomprobe"));
            // 获取thread的threadlocalrandomsecondaryseed字段的内存偏移地址
            secondary = unsafe.objectfieldoffset
                (tk.getdeclaredfield("threadlocalrandomsecondaryseed"));
        } catch (exception ex) {
   throw new error(ex); }
    }
}

类的构造函数

// 私有构造函数,无法被实例化
private locksupport() {
  }

前面简单的介绍了一下locksupport定义。接下来我们介绍java中三种阻塞和唤醒机制,并总结它们的优缺点。

方法一:使用object中的wait()方法让线程等待,使用object的notify()方法唤醒线程,结合synchronized;
方法二:使用juc包中的condition的await()方法让线程等待,使用signal()方法唤醒线程;
方法三:locksupport类可以阻塞当前线程以及唤醒指定被阻塞的线程;

我们用具体实例演示三种方法

方法一 使用wait()和notify():

public class objectwait {
  
    public static void main(string[] args) {
  
        object o = new object();
        thread t = new thread(new runnable() {
  
            @override
            public void run() {
  
                system.out.println("线程a被o.wait()阻塞前");
                synchronized(o){
  
                    try {
  
                        o.wait();
                    } catch (interruptedexception e) {
  
                        e.printstacktrace();
                    }
                }
                system.out.println("线程a被线程b o.notify()唤醒");
            }
        },"a");
        t.start();
        try {
  
            thread.sleep(100);
        } catch (interruptedexception e) {
  
            e.printstacktrace();
        }
        new thread(new runnable() {
  
            @override
            public void run() {
  
                system.out.println("线程b唤醒线程a");
                synchronized (o){
  
                    o.notify();
                }
            }
        },"b").start();
    }
}

结果:

线程a被o.wait()阻塞前
线程b唤醒线程a
线程a被线程b o.notify()唤醒

我们通过o.wait()将线程a阻塞,再通过线程b中运行o.notify()方法将线程a唤醒.
注意:1、wait和notify都需要在同步块或者同步方法里,也就是要使用到synchronized,将资源类锁住,且必须成对出现。 2、使用时必须先wait 在notify,否则wait不会被唤醒的情况,从而导致线程一直阻塞。
这里我没有演示 先notify再wait 会出现的wait不会唤醒的情况,大家可以自行测试。

方法二: lock .condition

public class conditionawait {
  
    public static void main(string[] args) {
  
        lock lock = new reentrantlock();
        condition condition = lock.newcondition();
        new thread(new runnable() {
  
            @override
            public void run() {
  
                system.out.println("线程a被condition.await()阻塞前");
                try {
  
                    lock.lock();
                    condition.await();
                } catch (interruptedexception e) {
  
                    e.printstacktrace();
                }finally {
  
                    lock.unlock();
                }
                system.out.println("线程a被线程b condition.signl()唤醒");
            }
        }, "a").start();
        new thread(new runnable() {
  
            @override
            public void run() {
  
                try {
  
                    lock.lock();
                    system.out.println("线程b中使用condition.signal()唤醒线程a");
                    condition.signal();
                }catch (exception e){
  
                }finally {
  
                    lock.unlock();
                }
            }
        }, "b").start();
    }
}
结果:
线程a被condition.await()阻塞前
线程b中使用condition.signal()唤醒线程a
线程a被线程b condition.signl()唤醒

注意:1 、condition中的线程等待和唤醒一定要先获得锁。
2、一定要先await,再signal,不能反了

方法三,使用locksupport

public class locksupportdemo {
  
    public static void main(string[] args) {
  
        thread t = new thread(new runnable() {
  
            @override
            public void run() {
  
                system.out.println("线程a被locksupport.park()阻塞");
                locksupport.park();
                system.out.println("线程a被线程b locksupport.unpark()唤醒");
            }
        },"a");
        t.start();
        
        new thread(new runnable() {
  
            @override
            public void run() {
  
                system.out.println("线程b唤醒线程a");
                // 唤醒指定线程t,也就是a
                locksupport.unpark(t);
            }
        },"b").start();
    }
}
结果:
线程a被locksupport.park()阻塞
线程b唤醒线程a
线程a被线程b locksupport.unpark()唤醒

从上面可以看出使用locksupport 进行线程阻塞和唤醒可以在线程的任意地方执行,并且可以通过unpart(thread)唤醒指定的线程。作为工具类locksupport的使用,也降低了代码的耦合性。

使用interrupt() 中断park()阻塞

package completefuture;
import java.util.concurrent.locks.locksupport;
public class locksupportdemo {
  
    public static void main(string[] args) {
  
        thread t = new thread(new runnable() {
  
            @override
            public void run() {
  
                system.out.println("before park");
                locksupport.park();
                system.out.println("after park");
            }
        },"a");
        t.start();
       //确保 park()执行
        try {
  
            thread.sleep(3000);
        } catch (interruptedexception e) {
  
            e.printstacktrace();
        }
//        system.out.println("线程t是否被阻塞: " t.isinterrupted());
        system.out.println("before interrupted");
        t.interrupt();
        system.out.println("after interrupted");
    }
}
结果:
before park
before interrupted
after interrupted
after park

三种方法的总结

方法 特点 缺点
wait/notify wait和notify都需要在同步块或者同步方法里,也就是要使用到synchronized,将资源类锁住,且必须成对出现。 2、使用时必须先wait 在notify,否则wait不会被唤醒的情况,从而导致线程一直阻塞。 需要借助synchronized
condition 需要结合lock 和unlock ,可以精准唤醒指定线程(示例没有展示),大家自行研究 它的底层其实还是使用的locksupport
locksupport 使用park 和unpark唤醒指定线程 ,不关系是先执行 unpark 还是park,只要是成对出现线程都将被释放 多次调用unpark也只能释放一次

locksupport中方法如下:

3.1 park() 源码分析

/**disables the current thread for thread scheduling purposes unless the permit is available.
if the permit is available then it is consumed and the call returns immediately; otherwise the current thread becomes disabled for thread scheduling purposes and lies dormant until one of three things happens:
some other thread invokes unpark with the current thread as the target; or
some other thread interrupts the current thread; or
the call spuriously (that is, for no reason) returns.
this method does not report which of these caused the method to return. callers should re-check the conditions which caused the thread to park in the first place. callers may also determine, for example, the interrupt status of the thread upon return. 
*/
public static void park() {
  
        unsafe.park(false, 0l);
    }

上面的方法如何理解呢?
如果没有permit许可,那么调用该方法后,当前线程立马停止执行计划(阻塞),直到有一下3中情况发生:
1、其他线程调用unpark(被阻塞线程引用)方法,参数为需要唤醒的线程;
2、其他线程中断当前线程;
3、调用虚假(即无缘无故)返回;

unsafe.park(isabsolute,timeout)的理解,阻塞一个线程直到unpark出现、线程

  • 被中断或者timeout时间到期。如果一个unpark调用已经出现了,

  • 这里只计数。timeout为0表示永不过期.当isabsolute为true时,

  • timeout是相对于新纪元之后的毫秒。否则这个值就是超时前的纳秒数。这个方法执行时

  • 也可能不合理地返回(没有具体原因)
    深入理解sun.misc.unsafe原理

3.2 unpark(thread thread)

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

给指定的线程提供unblock凭证。如果指定的线程使用了park(),则线程变成非阻塞。如果没有使用park,则线程下一次使用park时,怎线程不会阻塞。

park(blocker) 锁定指定对象

public static void park(object blocker) {
  
    // 获取当前线程
    thread t = thread.currentthread();
    // 设置blocker
    setblocker(t, blocker);
    // 获取许可
    unsafe.park(false, 0l);
    // 重新可运行后再此设置blocker
    setblocker(t, null);
}

说明: 调用park函数时,首先获取当前线程,然后设置当前线程的parkblocker字段,即调用setblocker函数,之后调用unsafe类的park函数,之后再调用setblocker函数。那么问题来了,为什么要在此park函数中要调用两次setblocker函数呢? 原因其实很简单,调用park函数时,当前线程首先设置好parkblocker字段,然后再调用unsafe的park函数,此后,当前线程就已经阻塞了,等待该线程的unpark函数被调用,所以后面的一个setblocker函数无法运行,unpark函数被调用,该线程获得许可后,就可以继续运行了,也就运行第二个setblocker,把该线程的parkblocker字段设置为null,这样就完成了整个park函数的逻辑。如果没有第二个setblocker,那么之后没有调用park(object blocker),而直接调用getblocker函数,得到的还是前一个park(object blocker)设置的blocker,显然是不符合逻辑的。总之,必须要保证在park(object blocker)整个函数执行完后,该线程的parkblocker字段又恢复为null。所以,park(object)型函数里必须要调用setblocker函数两次。setblocker方法如下。

5.1 thread.sleep()和object.wait()的区别 thread.sleep()不会释放占有的锁,object.wait()会释放占有的锁;

  • thread.sleep()必须传入时间,object.wait()可传可不传,不传表示一直阻塞下去;
  • thread.sleep()到时间了会自动唤醒,然后继续执行;
  • object.wait()不带时间的,需要另一个线程使用object.notify()唤醒;
  • object.wait()带时间的,假如没有被notify,到时间了会自动唤醒,这时又分好两种情况,一是立即获取到了锁,线程自然会继续执行;二是没有立即获取锁,线程进入同步队列等待获取锁;
  • 其实,他们俩最大的区别就是thread.sleep()不会释放锁资源,object.wait()会释放锁资源。

5.2 object.wait()和condition.await()的区别

  • object.wait()和condition.await()的原理是基本一致的,不同的是condition.await()底层是调用locksupport.park()来实现阻塞当前线程的。
  • 实际上,它在阻塞当前线程之前还干了两件事,一是把当前线程添加到条件队列中,二是“完全”释放锁,也就是让state状态变量变为0,然后才是调用locksupport.park()阻塞当前线程。

5.3 thread.sleep()和locksupport.park()的区别

  • locksupport.park()还有几个兄弟方法——parknanos()、parkutil()等,我们这里说的park()方法统称这一类方法。
  • 从功能上来说,thread.sleep()和locksupport.park()方法类似,都是阻塞当前线程的执行,且都不会释放当前线程占有的锁资源;
  • thread.sleep()没法从外部唤醒,只能自己醒过来;
  • locksupport.park()方法可以被另一个线程调用locksupport.unpark()方法唤醒;
  • thread.sleep()方法声明上抛出了interruptedexception中断异常,所以调用者需要捕获这个异常或者再抛出;
  • locksupport.park()方法不需要捕获中断异常;
  • thread.sleep()本身就是一个native方法; locksupport.park()底层是调用的unsafe的native方法;

5.4 object.wait()和locksupport.park()的区别 二者都会阻塞当前线程的运行,他们有什么区别呢?

  • 经过上面的分析相信你一定很清楚了,真的吗? 往下看!
  • object.wait()方法需要在synchronized块中执行; locksupport.park()可以在任意地方执行;
  • object.wait()方法声明抛出了中断异常,调用者需要捕获或者再抛出
  • locksupport.park()不需要捕获中断异常;
  • object.wait()不带超时的,需要另一个线程执行notify()来唤醒,但不一定继续执行后续内容;
  • locksupport.park()不带超时的,需要另一个线程执行unpark()来唤醒,一定会继续执行后续内容;
  • park()/unpark()底层的原理是“二元信号量”,你可以把它相像成只有一个许可证的semaphore,只不过这个信号量在重复执行unpark()的时候也不会再增加许可证,最多只有一个许可证。

5.5 如果在wait()之前执行了notify()会怎样?

  • 如果当前的线程不是此对象锁的所有者,却调用该对象的notify()或wait()方法时抛出illegalmonitorstateexception异常;
  • 如果当前线程是此对象锁的所有者,wait()将一直阻塞,因为后续将没有其它notify()唤醒它。

5.6 如果在park()之前执行了unpark()会怎样?

线程不会被阻塞,直接跳过park(),继续执行后续内容

locksupport.park()会释放锁资源吗?

不会,它只负责阻塞当前线程,释放锁资源实际上是在condition的await()方法中实现的。

网站地图