java的 wait notify sleep LockSupport.park 到底怎么区分

前言

之前对这些方法一直是云里雾里,最近正好看到相关的东西,赶紧重新整理一波。

wait & notify

wait

这是Object的方法,不允许重写。总结一下它的特点:

  • 带参数的是等待一定时间,不带参数的是无限等待
  • a.wait() 的时候,当前线程一定是持有了a的锁,否则会报错;所以a.wait方法一般是在synchronized(a){}内部执行的(也可以是用其他方式获得锁)
    所以,就有了一个有趣的问题:notify()会立刻释放锁么?
  • a.wait()的时候,当前线程会释放a的锁,让出cpu执行时间。但是当前线程持有的其他锁不会释放

notify

这也是Object的方法,它是用来唤醒在某个对象的monitor上等待的线程。

  • notify的唤醒顺序是随机的吗?
  • notify唤醒线程之后,线程会立即执行吗?
    • 不一定,取决于两个因素:(1)被唤醒的线程首先需要获取锁,如果不能获取到锁,也不会执行(2)被唤醒的线程获取到锁之后,如果操作系统没有调度到它,也不会执行。
  • notify先于wait方法执行,会怎样?
    • 不会报错,但是会让调用wait的线程得不到唤醒。。
  • notifyAll 是怎么唤醒线程的?
    • a.notifyAll是唤醒全部调用了a.wait等待锁的线程
    • 唤醒线程是LIFO的,会先唤醒等待队列的最后一个线程,然后这些线程依次去唤醒其前面的线程(不确定)

为什么wait&notify要用同步块

因为著名的Lost wake-up problem,这个问题本以为很难,但是看了Java 多线程中的「lost wake up 问题」瞬间理解。

等待池 & 锁池

调用a.wait之后,当前线程会进入到一个“等待池”,等待被唤醒;而被唤醒之后会进入到“锁池”,等待获取a的对象锁。
这两个概念可能不是那么流行,但是可以帮助我们理解线程执行wait方法之后发生了什么。而且notify()是随机唤醒线程么? 这篇文章里,我们看到第一次实验的结果中,线程虽然不是顺序执行,但也不是绝对随机,而是有一部分是倒序的。

用等待池和锁池两个概念就可以解释这个问题了。等待池的线程会被顺序唤醒,被唤醒之后进入到锁池队列,但是main线程这时候很有可能还在持有锁,导致被唤醒的线程无法执行。main线程就会继续唤醒线程加入到锁池里。当main线程不再持有锁,锁池里的线程开始抢到锁,就会开始执行。而根据这时候的倒序可以推测:锁池的顺序可能是LIFO,于是产生了线程的倒序执行。

参考知乎问答

sleep

这是Thread的方法,必须指定参数。用于线程睡眠一定时间。

  • sleep期间会让出cpu吗?
    • 当然,不然就太浪费CPU了。而且可以验证一下,线程sleep之后看一下其cpu使用率会很低
  • sleep指定时间之后,会立即执行吗?
    • 不一定,只是会参与调度,具体是否执行需要由操作系统决定
  • sleep之后线程进入什么状态
    • TIMED_WAITING(Thread.State类里有所有状态)
    • 到时间之后会变为RUNNABLE状态等待调度。
  • sleep之后会释放锁吗?
    • 不会,即便是wait方法也只是释放调用wait方法的对象对应的锁。

LockSupport.park

这是JDK新提供的方法,可以使线程等待一段时间,或者永久等待,类似wait

  • 会释放锁吗?
    • 不会,和sleep一样,它没有锁的概念。
    • Condition是锁生成的,它调用await的时候本质上也是调用LockSupport.park方法;它会释放对应的锁
  • unpark先于park执行会怎样?
    • park执行的时候就不会等待了,直接继续执行
  • 底层实现
    • 调用Unsafe.park,这是一个native方法
    • 本质上是一个信号量,只会取值为0和1
  • 多次调用unpark会发生什么
    • 和单次调用unpark一样,把信号量置为1,只能唤醒一次park
    • 下面的示例可以验证这个逻辑,t线程只能打印出1和2,也就是只被唤醒了一次。
public static void main(String[] args) throws InterruptedException, IOException {
        CountDownLatch countDownLatch1 = new CountDownLatch(1);
        CountDownLatch countDownLatch2 = new CountDownLatch(1);

        Thread t = new Thread(() -> {
            try {
                countDownLatch2.countDown();
                countDownLatch1.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("1");
            LockSupport.park();
            System.out.println("2");
            LockSupport.park();
            System.out.println("3");
        });
        t.start();
        countDownLatch2.await();
        LockSupport.unpark(t);
        LockSupport.unpark(t);
        countDownLatch1.countDown();
    }

并发工具优先于 wait 和 notify

参考effective-java-thirf-edition

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: LockSupport.park()和条件变量都是Java中用于线程同步的机制,但是它们有一些不同点。 LockSupport.park()是一个底层的线程阻塞工具,它可以让线程处于等待状态,直到另一个线程调用unpark()方法唤醒它。LockSupport.park()和unpark()方法可以用于实现高级同步工具,如ReentrantLock、Semaphore等。 条件变量是基于锁的高级同步工具,与LockSupport.park()不同的是,条件变量可以提供更丰富的同步语义。条件变量通常与锁一起使用,当某个线程需要等待某个条件满足时,它会释放锁并进入等待状态,当另一个线程满足条件时,它会通知条件变量,从而唤醒等待的线程。条件变量的实现可以使用Object.wait()和Object.notify()方法,但是使用条件变量可以更加简洁和安全。 总的来说,LockSupport.park()适用于更底层和高级的同步需求,而条件变量适用于更高级和丰富的同步需求。 ### 回答2: LockSupport.park()和条件变量是两种不同的线程同步机制。 LockSupport.park()是Java并发包中的一个工具类,用于实现线程的阻塞和唤醒。调用LockSupport.park()方法可以使当前线程进入阻塞状态,而调用LockSupport.unpark()方法可以释放线程的阻塞状态。与其他同步机制不同的是,LockSupport.park()和LockSupport.unpark()并没有对应的锁对象,因此可以随意进行调用,而不需要在某个特定的锁上进行等待或唤醒操作。这使得LockSupport.park()和LockSupport.unpark()可以更加灵活地进行线程的阻塞和唤醒操作,而不会受到特定锁的限制。 条件变量是一种线程同步的机制,它允许线程在满足特定条件之前进行阻塞,并且在条件满足时被唤醒。条件变量通常与互斥锁配合使用,在互斥锁上对条件进行等待和唤醒操作。当某个线程获取到互斥锁后,发现条件不满足时,可以调用条件变量的等待方法将自己阻塞住,并且释放对互斥锁的持有。其他线程在满足条件时可以通过唤醒方法来唤醒等待的线程,使其重新竞争互斥锁。 LockSupport.park()和条件变量在实现的方式上有所不同。LockSupport.park()是通过调用底层操作系统提供的线程阻塞原语来实现的,而条件变量则是通过在用户态进行等待和唤醒操作来实现的。此外,LockSupport.park()更加的灵活,不需要在特定的锁上进行等待或唤醒操作,而条件变量需要依赖于互斥锁。因此,在选择使用哪一种线程同步机制时,可以根据具体的场景需求和性能要求来进行选择。 ### 回答3: LockSupport.park()和条件变量都是Java中用于线程同步的机制,但它们有一些区别。 LockSupport.park()是一个基于线程的阻塞方法,调用该方法将导致当前线程进入阻塞状态,直到满足某个条件才能继续执行。它不依赖于任何锁对象,可以直接调用。与传统的锁机制相比,它更加灵活,不需要获取锁就能进行阻塞和唤醒操作。通常,我们可以通过调用LockSupport.unpark()方法来唤醒被park()阻塞的线程。但需要注意的是,unpark()方法只是给线程一个许可,线程不会保证立即从park()方法返回,因此,必要时我们需要通过其他方式确保条件满足后的唤醒。 而条件变量是基于锁的一个高级概念,它是指一个对象的状态依赖于某个条件的一种机制。通过显式地调用锁对象的wait()方法来进行等待,而该对象的状态由其他线程通过锁对象的notify()或notifyAll()方法来改变。条件变量的使用必须搭配锁对象,一般是使用ReentrantLock来实现。与LockSupport相比,条件变量的使用方式更加繁琐,需要先获取锁对象才能调用wait()和notify()等方法。 总结起来,LockSupport.park()和条件变量都是用于线程同步的机制,但前者更加灵活简洁,不依赖于特定的锁对象,可以直接进行阻塞和唤醒操作;后者是基于锁的高级概念,需要搭配锁对象使用,通过wait()和notify()等方法来实现条件的等待和改变。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值