前言:
理解线程常用方法,有助于我们对多线程有更深入的理解,你都知道哪些线程常用的方法,各自又有什么作用?本文将对线程 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 类中,这样就合情合理了。
如有错误的地方欢迎指出纠正。