线程常用方法你了解吗(sleep、wait、notify、notifyAll)

前言:

理解线程常用方法,有助于我们对多线程有更深入的理解,你都知道哪些线程常用的方法,各自又有什么作用?本文将对线程 sleep、wait、notify、notifyAll 方法进行详解。

常用方法:

  • sleep:线程休眠。
  • wait:线程等待。
  • notify:单个线程唤醒。
  • notifyAll:唤醒所有线程。
  • join:线程强占。
  • yield:线程让步。
  • interrupt:线程打断。

线程常用方法你了解吗(join、yield、interrupt)

sleep 方法:

让线程休眠指定的时间,时间到了线程将会恢复执行,需要注意的是 sleep 方法会让线程让出 cpu 时间片,但是不是释放持有的锁,如果持有锁对象的线程调用了 sleep 方法,那其他线程一样得不到这个锁对象。

sleep 方法演示:

public class ThreadDemo extends Thread {

    private int ticket = 5;

    @Override
    public void run() {
        synchronized (ThreadDemo.class) {
            while (ticket > 0) {
                //休眠两秒
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("当前锁对象:" + ThreadDemo.class + ",当前线程名称:" + Thread.currentThread().getName() + ",当前还剩下电影票:" + ticket-- + "张");
            }
        }
    }

    public static void main(String[] args) {
        ThreadDemo threadDemoOne = new ThreadDemo();
        threadDemoOne.start();
        ThreadDemo threadDemoTwo = new ThreadDemo();
        threadDemoTwo.start();
    }
}

执行结果:

当前锁对象:class com.zt.dc.portal.admin.web.service.impl.leadtimereport.ThreadDemo,当前线程名称:Thread-0,当前还剩下电影票:5张
当前锁对象:class com.zt.dc.portal.admin.web.service.impl.leadtimereport.ThreadDemo,当前线程名称:Thread-0,当前还剩下电影票:4张
当前锁对象:class com.zt.dc.portal.admin.web.service.impl.leadtimereport.ThreadDemo,当前线程名称:Thread-0,当前还剩下电影票:3张
当前锁对象:class com.zt.dc.portal.admin.web.service.impl.leadtimereport.ThreadDemo,当前线程名称:Thread-0,当前还剩下电影票:2张
当前锁对象:class com.zt.dc.portal.admin.web.service.impl.leadtimereport.ThreadDemo,当前线程名称:Thread-0,当前还剩下电影票:1张
当前锁对象:class com.zt.dc.portal.admin.web.service.impl.leadtimereport.ThreadDemo,当前线程名称:Thread-1,当前还剩下电影票:5张
当前锁对象:class com.zt.dc.portal.admin.web.service.impl.leadtimereport.ThreadDemo,当前线程名称:Thread-1,当前还剩下电影票:4张
当前锁对象:class com.zt.dc.portal.admin.web.service.impl.leadtimereport.ThreadDemo,当前线程名称:Thread-1,当前还剩下电影票:3张
当前锁对象:class com.zt.dc.portal.admin.web.service.impl.leadtimereport.ThreadDemo,当前线程名称:Thread-1,当前还剩下电影票:2张
当前锁对象:class com.zt.dc.portal.admin.web.service.impl.leadtimereport.ThreadDemo,当前线程名称:Thread-1,当前还剩下电影票:1张

分析结果可知,Thread-0 先获取到锁对象,即使 Thread-0 每次休眠 2 秒钟,共进行了 5 次休眠,Thread-1 还是没有获取到锁对象,证明 sleep 是不释放锁对象的的。

wait 、notify、notifyAll 方法:

wait 和 notify 方式是 Object 类的方法,用于线程的等待与唤醒,必须搭配 synchronized 锁来使用,如果没有搭配 synchronized 使用,会直接抛出异常,wait 、notify、notifyAll 方法特点如下:

  • wait 方法会让线程进入等待,且释放锁,注意 sleep 方法是不会释放锁的,wait 方法支持等待时间设置,在等待时间内被唤醒则等待队列重新竞争锁,如果再等待时间内没有被唤醒,等待时间到了自动唤醒。
  • notify 方法会随机唤醒一个等待的线程,被唤醒的线程会进入等待队列重新竞争锁,可能会造成死锁。
  • notifyAll 方法会唤醒所有等待的线程,被唤醒的线程会进入等待队列重新竞争锁。

wait 方法源码:

 public final void wait() throws InterruptedException {
        wait(0);
    }
    
 public final void wait(long timeout, int nanos) throws InterruptedException {
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos > 0) {
           //只是进行了 timeout +1 操作
            timeout++;
        }

        wait(timeout);
    }

    public final native void wait(long timeout) throws InterruptedException;

源码解读:

void wait():调用者线程等待,直到其他线程为此线程调用 notify 方法或 notifyAll 方法,根据源码可知,wait
方法实际调用的是调用wait(0L)。
void wait(long timeout) :调用者线程等待指定时间,直到其他线程为此线程调用 notify 方法或 notifyAll 方法,或者已经过了指定的时间。
void wait(long timeout, int nanos): 调用者线程等待指定时间,直到其他线程为此线程调用 notify 方法或 notifyAll 方法,或者其他线程中断当前线程,方法提供了一个 nanos 值更好的控制等待的时间,但是根据源码来看,其实并没有真正使用这个值。

代码演示如下:

package com.zt.dc.portal.admin.web.service.impl.leadtimereport;

/**
 * @ClassName: ThreadDemo
 * @Author: zhangyong
 * @Date: 2024/3/22 14:11
 * @Description:
 */
public class ThreadDemo extends Thread {

    public Object lock;

    public ThreadDemo(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {

        synchronized (lock) {
            System.out.println("当前锁对象:" + lock + ",当前线程准备进入等待,线程名称:" + Thread.currentThread().getName()+ ",当前时间:"+  System.currentTimeMillis() / 1000);
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("业务执行完毕,当前锁对象:" + lock + ",当前线程名称:" + Thread.currentThread().getName()+ ",当前时间:"+  System.currentTimeMillis() / 1000);
        }
    }


    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        ThreadDemo threadDemoOne = new ThreadDemo(lock);
        threadDemoOne.start();
        ThreadDemo threadDemoTwo = new ThreadDemo(lock);
        threadDemoTwo.start();
        //休眠2秒再执行 notify 线程
        Thread.sleep(2000);
        ThreadNotifyDemo threadDemoNotify = new ThreadNotifyDemo(lock);
        threadDemoNotify.start();

    }
}

public class ThreadNotifyDemo extends Thread {

    public Object lock;

    public ThreadNotifyDemo(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println("唤醒线程获取到锁,准备唤醒线程" + ",当前线程名称:" + Thread.currentThread().getName() + ",当前时间:" + System.currentTimeMillis() / 1000);
            //lock.notify();
            lock.notifyAll();
            System.out.println("唤醒线程完毕,当前锁对象:" + lock + ",当前线程名称:" + Thread.currentThread().getName() + ",当前时间:" + System.currentTimeMillis() / 1000);
        }
    }
    
}

使用 notifyAll 唤醒测试结果:

当前锁对象:java.lang.Object@37ce62b5,当前线程准备进入等待,线程名称:Thread-1,当前时间:1711181455
当前锁对象:java.lang.Object@37ce62b5,当前线程准备进入等待,线程名称:Thread-0,当前时间:1711181455
唤醒线程获取到锁,准备唤醒线程,当前线程名称:Thread-2,当前时间:1711181457
唤醒线程完毕,当前锁对象:java.lang.Object@37ce62b5,当前线程名称:Thread-2,当前时间:1711181457
业务执行完毕,当前锁对象:java.lang.Object@37ce62b5,当前线程名称:Thread-0,当前时间:1711181457
业务执行完毕,当前锁对象:java.lang.Object@37ce62b5,当前线程名称:Thread-1,当前时间:1711181457

使用 notify 唤醒测试结果:

当前锁对象:java.lang.Object@7e3a5d26,当前线程准备进入等待,线程名称:Thread-0,当前时间:1711182561
当前锁对象:java.lang.Object@7e3a5d26,当前线程准备进入等待,线程名称:Thread-1,当前时间:1711182561
唤醒线程获取到锁,准备唤醒线程,当前线程名称:Thread-2,当前时间:1711182563
唤醒线程完毕,当前锁对象:java.lang.Object@7e3a5d26,当前线程名称:Thread-2,当前时间:1711182563
业务执行完毕,当前锁对象:java.lang.Object@7e3a5d26,当前线程名称:Thread-0,当前时间:1711182563

可以看到使用 notify 唤醒只唤醒了一个线程,而使用 notifyAll 则唤醒了所有的线程,验证了我们上面的观点。

sleep 和 wait 方法的区别?

  • sleep 方法是 Thread 类的静态方法,wait 是 Object 类的 native 方法。
  • sleep 方法不会释放锁,wait 方法会释放锁,调用 wait 方法的线程重新进入等待队列。
  • sleep 方法不依赖 synchronized 关键字,wait 方法依赖 synchronized 关键字。
  • sleep 方法用于当前线程的休眠,wait 方法用于多线程之间的通讯。
  • sleep 方法无需唤醒,wait 方法需要被唤醒(没有指定等待时间)。

为什么wait notify会放在Object里边?wait、notify、notifyAll 用来操作线程为什么定义在Object类中?

因为这些方法都存在于同步中,依赖 synchronized 关键字,使用这些方法必须标识同步所属的锁,锁可以是任意对象,所以任意对象调用方法定义在 Object 类中,这样就合情合理了。

如有错误的地方欢迎指出纠正。

  • 18
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值