线程休眠
sleep
sleep线程休眠方式
sleep线程休眠一共有 3 种方式:
- 方式一:
Thread.sleep(1000);
- 方式二:
TimeUnit.SECONDS.sleep(1);
(SECONDS为秒单位,还有day,month等) - 方法三:
Thread.sleep(TimeUnit.SECONDS.toMillis(1));
休眠当前线程
方法 | 说明 |
---|---|
public static void sleep(long millis) throws InterruptedException | 休眠当前线程 millis 毫秒 |
public static void sleep(long millis, int nanos) throws InterruptedException | 可以更高精度的休眠 |
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println(System.currentTimeMillis());
Thread.sleep(3 * 1000);
System.out.println(System.currentTimeMillis());
}
}
sleep 休眠缺点:必须传递一个明确的结束数据
wait
线程通讯机制
线程通讯机制:一个线程的动作可以让另一个线程感知到就叫做线程通讯机制
wait(休眠)/ notify(唤醒)/ notifyAll(唤醒全部)
wait举例
我们让wait无限期休眠,1秒后让主线程唤醒它
public class ThreadDemo38 {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Object lock2 = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程1:进入线程方法");
synchronized (lock) {
try {
//线程休眠
lock.wait();
// Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程1:执行完成");
}
});
t1.start();
Thread.sleep(1000);
System.out.println("主线程唤醒线程1");
synchronized (lock2) {
//唤醒线程
lock2.notify();
}
}
}
wait/ notify/ notifyAll 使用注意事项
- 在使用以上方法的时候必须要加锁
- 加锁对象和 wait/ notify/ notifyAll 的对象必须保持一致,否则会报错
- 一组wait和 notfiy / notifiyAll 必须时同一对象
- notifyAll 只能唤醒当前对象的所有等待线程
wait方法
方法 | 描述 |
---|---|
wait(long):void | 毫秒级别最大等待时间 |
wait(long, int):void | 纳秒级别最大等待时间 |
wait(l):void | 调用wait(0),表示永久等待 |
如果线程有最大等待时间,在等待期间没有被唤醒,则会在最大等待时间时自己醒来
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Object lock2 = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程1:进入休眠"+new Date());
synchronized (lock) {
try {
//线程休眠
lock.wait(3000);
// Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程1:执行完成"+new Date());
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程2:进入休眠"+new Date());
synchronized (lock) {
try {
//线程休眠
lock.wait(3000);
// Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程2:执行完成"+new Date());
}
});
t2.start();
Thread.sleep(2000);
System.out.println("主线程唤醒线程1和线程2");
// synchronized (lock) {
// //唤醒线程
// lock.notifyAll();
// }
}
这里线程自动醒来
可以看到,线程自动在3秒后醒来了
如果提前唤醒他,
可以看到,线程在2秒后被唤醒
面试问题
wait 为什么要加锁?
wait在使用的时候需要释放锁,在释放锁之前必须要有一把锁,所以要加锁
wait 为什么要释放锁?
wait默认是不传任何值的,当不传递任何值得时候表示永久等待,这样就会造成一把锁被一个线程一直持有,为了避免这种问题的方式,所以在使用 wait 时一定要释放锁
Thread.sleep(0) 和 Object.lock(0) 区别
- sleep 它是 Thread 的静态方法;而 Lock 是 Object 的方法;
- sleep(0) 立即触发一次 CPU 资源的抢占;而 lock(0) 是永久等待下去
wait 和 sleep 的区别
相同点:
- 都可以让当前线程休眠
- 都必须要处理一个 Interrupt 异常
不同点:
- wait 来自与 Object 中的一个方法;而 sleep 来自于 Thread
- 传参不同,wait 可以没有参数,而 sleep 必须有一个大于等于 0 的参数
- wait 使用必须加锁,sleep 使用时不用加锁
- wait 使用时会释放锁,而 sleep 使用时不会释放锁
- wait 默认不传参的情况下会进入 WAITING 状态,而 sleep 会进入 TIME_WAITING 状态
为什么 wait 会放到 Object 中而不是 Thread 中?
wait 必须要加锁和释放锁,而锁又是属于对象级别的,而不是线程级别的(线程和锁是一对多的关系,也就是一个线程可以拥有多把锁),为了灵活起见(一个线程当中会有多把锁),就把 wait 放在 Object 当中
LockSupport
不传参
不传参默认是 无限期休眠,WAITING状态
我们想指定唤醒某个线程的时候,这时候notify就不能够满足了,因为它是随机唤醒的
我们可以用 LockSupport.park(); 方法让线程休眠
用LockSupport.unpark(t1); 方法让指定线程唤醒
Object lock = new Object();
Object lock2 = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程1:进入休眠"+new Date());
LockSupport.park();
System.out.println("线程1:执行完成"+new Date());
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程2:进入休眠"+new Date());
LockSupport.park();
System.out.println("线程2:执行完成"+new Date());
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程3:进入休眠"+new Date());
LockSupport.park();
System.out.println("线程3:执行完成"+new Date());
}
});
t1.start();
t2.start();
t3.start();
Thread.sleep(1000);
System.out.println("唤醒线程");
LockSupport.unpark(t1);
Thread.sleep(1000);
System.out.println("唤醒线程");
LockSupport.unpark(t2);
Thread.sleep(1000);
System.out.println("唤醒线程");
LockSupport.unpark(t3);
其次,LockSupport 虽然不用处理interrupt 异常,但是能正常感知 interrupt 异常
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程休眠前状态:" +
Thread.interrupted());
LockSupport.park();
System.out.println("线程休眠后状态:" +
Thread.interrupted());
}
});
t1.start();
Thread.sleep(1000);
//终止线程
t1.interrupt();
LockSupport.unpark(t1);
可以正常感知 interrupt 异常
传参
使用 LockSupport.parkUntil 方法进行传参
注意:此方法传参并不是和wait,sleep一样是等待多长时间停止休眠,而是具体的时间
比如我这里就是系统现在时间 + 1秒后进行唤醒
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程进入休眠:" + new Date());
LockSupport.parkUntil(System.currentTimeMillis() + 1000);
System.out.println("线程终止休眠:" + new Date());
}
}).start();
wait 和 LockSupport 的区别
相同点 :
- 二者都可以让线程进行休眠
- 二者都可以传参或者不传参,并且二者线程状态也是一致的
不同点:
- wait 必须要配合 synchronized 一起使用(必须加锁),而 LockSupport 不用加锁
- wait 只能唤醒全部和随机的线程,而 LockSupport 可以唤醒指定的线程