一、sleep和wait区别
区别一:语法使用不同
wait 方法必须配合 synchronized 一起使用,不然在运行时就会抛出 IllegalMonitorStateException 的异常
而 sleep 可以单独使用,无需配合 synchronized 一起使用。
区别二:所属类不同
wait 方法属于 Object 类的方法,而 sleep 属于 Thread 类的方法
区别三:唤醒方式不同
sleep 方法必须要传递一个超时时间的参数,且过了超时时间之后,线程会自动唤醒。而 wait 方法可以不传递任何参数,不传递任何参数时表示永久休眠,直到另一个线程调用了 notify 或 notifyAll 之后,休眠的线程才能被唤醒。也就是说 sleep 方法具有主动唤醒功能,而不传递任何参数的 wait 方法只能被动的被唤醒。
区别四:释放锁资源不同
wait 方法会主动的释放锁,而 sleep 方法则不会。接下来我们使用代码的方式来演示一下二者的区别。
sleep 不释放锁
接下来使用 sleep 是线程休眠 2s,然后在另一个线程中尝试获取公共锁,如果能够获取到锁,则说明 sleep 在休眠时会释放锁,反之则说明不会释放锁,
在调用了 sleep 之后,在主线程里尝试获取锁却没有成功,只有 sleep 执行完之后释放了锁,主线程才正常的得到了锁,这说明 sleep 在休眠时并不会释放锁。
wait 释放锁
接下来使用同样的方式,将 sleep 替换成 wait,在线程休眠之后,在另一个线程中尝试获取锁,
当调用了 wait 之后,主线程立马尝试获取锁成功了,这就说明 wait 休眠时是释放锁的。
区别五:线程进入状态不同
调用 sleep 方法线程会进入 TIMED_WAITING 有时限等待状态,而调用无参数的 wait 方法,线程会进入 WAITING 无时限等待状态。
总结
sleep 和 wait 都可以让线程进入休眠状态,并且它们都可以响应 interrupt 中断,但二者的区别主要体现在:语法使用不同、所属类不同、唤醒方式不同、释放锁不同和线程进入的状态不同。
二、线程的状态Thread.State
三、供参考的多线程代码一、sleep和wait(WAITING和TIMED_WAITING状态)
public class Test04_ThreadState {
public static void main(String[] args) {
new Thread(new TimeWaiting(), "TimeWaitingThread").start();
new Thread(new Waiting(), "WaitingThread").start();
}
/**
* 该线程不断的进行睡眠
*/
static class TimeWaiting implements Runnable {
@Override
public void run() {
while (true) {
SleepUtils.second(100);
}
}
}
/**
* 该线程在Waiting.class实例上等待
*/
static class Waiting implements Runnable {
@Override
public void run() {
while (true) {
synchronized (Waiting.class) {
try {
Waiting.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
四、查看线程运行状态的方法
1.jps
获得线程号和线程的类名
2.jstack + 线程号
jstack 2636
看线程某个时刻的运行情况(线程的快照)
这里打印出来的是当前线程的快照dump。
3.jvisualvm(可视化软件)
在java的jdk目录下的bin目录下有jvisualvm.exe文件,可以
打开软件后可以看到正在运行的进程,如下图
可以在其中找到java代码创建的线程的名字对应的线程
可以看到WatingThread线程状态是WAITING,等待状态
TimeWaitingThread线程状态是TIMED_WAITING,超时等待状态
和我们这篇文章的 二、线程的状态Thread.State 对应。
五、案例
一、Blocked阻塞状态
Blocked阻塞状态代码
public class Test04_ThreadState {
public static void main(String[] args) {
new Thread(new Blocked(), "BlockedThread-1").start();
new Thread(new Blocked(), "BlockedThread-2").start();
}
/**
* 该线程在Blocked.class实例上加锁后,不会释放该锁
*/
static class Blocked implements Runnable {
@Override
public void run() {
synchronized (Blocked.class) {
while (true) {
SleepUtils.second(100);
}
}
}
}
}
Blocked阻塞状态代码jvisualvm查看
由于BlockedThread-1先创建,抢占到了系统资源,运行后拿到了锁,并调用了sleep方法,因此进入TIMED_WAITING超时等待状态,而BlockedThread-2拿不到锁,因此进入Blocked阻塞状态。线程快照dump如下图:
Blocked阻塞状态代码总结
1.触发Blocked状态的原因
由于BlockedThread-1先创建,抢占到了系统资源,运行后拿到了锁,并调用了sleep方法,因此进入TIMED_WAITING超时等待状态,而BlockedThread-2拿不到锁,因此进入Blocked阻塞状态。
2.sleep方法
由于sleep方法不会释放锁,因此BlockedThread-2无法拿到锁,进入阻塞状态,更加验证了sleep方法不会释放锁。
二、ReentrantLock可重入锁
ReentrantLock可重入锁代码
public class Test04_ThreadState {
private static Lock lock = new ReentrantLock();
public static void main(String[] args) {
new Thread(new Sync(), "SyncThread-1").start();
new Thread(new Sync(), "SyncThread-2").start();
}
static class Sync implements Runnable {
@Override
public void run() {
lock.lock();
try {
SleepUtils.second(100);
} finally {
lock.unlock();
}
}
}
}
ReentrantLock可重入锁代码jvisualvm查看
ReentrantLock可重入锁代码总结
1.synchronized和ReentrantLock区别(简略描述,之后会详细补充)
ReentrantLock锁更加面向对象
两个锁加锁之后另外一个线程进入状态不一样
synchronized进入blocked状态是被动的 还没有进入到同步代码块中
ReentrantLock是一种主动进入锁的状态 已经进入到代码块中 程序恢复之后 它会从等待的位置继续执行
最后总结:
a.线程的状态
jps看线程的线程号
jstack看线程某个时刻的运行情况(线程的快照)
jvisualvm对线程进行dump
b.线程调用sleep
进入TimeWaiting状态 不会释放锁
c.线程调用wait
进入Waiting状态 会释放锁
d.A线程进入B线程已经拿到锁(synchronized)
A线程会进入阻塞状态blocked状态
e.当A线程进入B线程已经拿到锁(lock)
A线程会进入等待状态waiting状态
f.synchronized和lock区别
1.lock锁更加面向对象
2.两个锁加锁之后另外一个线程进入状态不一样:
synchronized是blocked状态,lock是waiting状态
3.synchronized进入blocked状态是被动的 还没有进入到同步代码块中
4.lock是一种主动进入锁的状态 已经进入到代码块中 程序恢复之后 它会从等待的位置继续执行
如果你不太理解synchronized锁的对象是什么,你可以看下面这篇文章:
synchronized锁的对象是什么