Java 多线程编程中的等待和休眠:wait() 和 sleep() 方法解析

640

背景

在Java多线程中,线程的状态如上图所示(图来自JavaGuide),可以发现,当前线程调用wait(long time)方法或者执行Thread.sleep(long time)方法都会进入到一个TIMED_WAITING的状态。那么,这个状态下的线程的锁资源是怎么样的呢?

线程等待

在多线程环境下,如果当前线程调用了wait()方法,那么该方法会进入到WAITING状态,并释放它所持有的锁,等待其他线程调用notify()notifyAll()方法来唤醒它。如果没有其唤醒操作,当前线程会处于一个无限等待的状态。

如果当前线程在调用的是wait(long time)方法时,线程会进入到一个TIMED_WAITING状态,同时会释放锁资源,等待其他线程调用notify()notifyAll()方法来唤醒它,或者等待指定的时间过期后自动唤醒。在等待期间,该线程不会占用 CPU 资源,因此其他线程可以执行。

代码演示:

WaitingThread类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class WaitingThread extends Thread {
private final Object lock;
public WaitingThread(Object lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
System.out.println("Thread " + Thread.currentThread().getId() + "获取到了锁资源");
try {
// 等待2秒钟,释放锁资源
System.out.println("Thread " + Thread.currentThread().getId() +"线程等待2s.....释放锁");
lock.wait(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread " + Thread.currentThread().getId() + " 等待时间过去,唤醒,尝试重新获取锁资源");
}
}
}

WaitingTest类

1
2
3
4
5
6
7
8
9
public class WaitingTest {
public static void main(String[] args) {
Object lock = new Object();
WaitingThread thread1 = new WaitingThread(lock);
WaitingThread thread2 = new WaitingThread(lock);
thread1.start();
thread2.start();
}
}

演示结果

image-20230527102116952

上述代码中,我们创建了两个线程,并将它们传递给同一个锁对象。在run()方法中,线程首先使用synchronized关键字获取锁对象,然后调用wait(long time)方法进入TIMED_WAITING状态,等待2秒钟后会自动唤醒,此时,锁对象已经被释放,线程需要重新获取锁才能继续执行。可以看到,在线程重新获取锁之后,它就可以继续执行对共享数据的操作了。

线程休眠

当线程调用sleep(long time)方法时,该线程也会进入到TIMED_WAITING状态,但是当前线程对象并不会释放锁资源,因此其他线程对象也无法获取到锁资源,会进入阻塞状态。

当指定的休眠时间过期之后,当前线程会主动唤醒,并且该线程仍然持有锁资源,可以继续执行。

代码演示:

SleepingThread类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class SleepingThread extends Thread {
private final Object lock;

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

@Override
public void run() {
synchronized (lock) {
System.out.println("Thread " + Thread.currentThread().getId() + "获取到了锁资源");
try {
System.out.println("Thread " + Thread.currentThread().getId() +"线程休眠2s.....仍持有锁");
// 线程休眠2秒钟,进入TIMED_WAITING状态
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread " + Thread.currentThread().getId() + " 休眠时间过去,自动唤醒,继续执行");
System.out.println("---------------------------");
}
}
}

SleepingTest类

1
2
3
4
5
6
7
8
9
public class SleepingTest {
public static void main(String[] args) {
Object lock = new Object();
SleepingThread thread1 = new SleepingThread(lock);
SleepingThread thread2 = new SleepingThread(lock);
thread1.start();
thread2.start();
}
}

运行结果

image-20230527105237869

在上面的代码中,我们创建了两个线程,并将它们传递给同一个锁对象。在线程的run()方法中,我们首先使用synchronized关键字来获取锁对象,然后让线程进入TIMED_WAITING状态。在这里,线程会休眠2秒钟,然后再输出一条消息。

总结

当线程调用wait()方法时,线程会进入一个WAITING状态,并且释放锁资源,直至被唤醒才会进入到就绪状态,竞争锁资源。

当线程调用wait(long time)方法时(指定一个等待时间),线程会进入到一个TIMED_WAITING状态,同时释放锁资源,只有当线程被通知或者等待时间结束后才会重新去获取锁资源。

当线程调用的是sleep(long time)方法,线程虽然也会进入到一个TIMED_WAITING状态,但是不会释放锁资源,其他线程会处于阻塞状态直至当前线程释放锁资源