一个线程的动作可以让另一个线程感知到。(在Java面试的情况中,大多数情况下,线程间的通信机制指的是线程间的交互,即线程的 唤醒和 阻塞。并不是字面意义上的多个线程之间互相共享和交换数据的“通信”。)
wait()方法:
wait():使线程停止运行,进入等待状态。
public class ThreadDemo38 {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程1:进入线程方法。");
synchronized (lock) {
try {
// 线程休眠
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程1:执行完成!");
}
}, "t1");
t1.start();
Thread.sleep(1000);
System.out.println("主线程唤醒线程1");
synchronized (lock) {
// 唤醒线程
lock.notify();
}
}
}
notify()方法:
notify():使停止的线程继续运行(唤醒),该方法只能唤醒某一个等待线程。
notifyAll()方法:
notifyAll():使停止的线程继续运行(唤醒),但如果有多个线程都在等待中,那么该方法可以一次唤醒所有的等待线程。
public class ThreadDemo39 {
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(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程1:执行完成");
}
}, "t1");
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程2:进入休眠");
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程2:执行结束");
}
}, "t2");
t2.start();
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程3:进入休眠");
synchronized (lock2) {
try {
lock2.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程3:结束执行");
}
}, "t3");
t3.start();
Thread.sleep(2000);
System.out.println("主线程唤醒线程");
synchronized (lock) {
// 唤醒线程
lock.notifyAll();
}
}
}
LockSupport.park()方法:
该方法也是唤醒线程,但该方法可以唤醒指定的线程,而notify()和notifyAll()不能唤醒指定的线程。
import java.util.concurrent.locks.LockSupport;
public class ThreadDemo42 {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程1:进入休眠");
// 线程休眠
LockSupport.park();
System.out.println("线程1:执行完成");
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程2:进入休眠");
LockSupport.park();
System.out.println("线程2:执行结束");
}
}, "t2");
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程3:进入休眠");
LockSupport.park();
System.out.println("线程3:结束执行");
}
}, "t3");
t1.start();
t2.start();
t3.start();
Thread.sleep(100);
System.out.println("------------------");
LockSupport.unpark(t3);
Thread.sleep(100);
LockSupport.unpark(t1);
Thread.sleep(100);
//唤醒线程t2
LockSupport.unpark(t2);
}
}
LockSupport()注意事项:
import java.util.Date;
import java.util.concurrent.locks.LockSupport;
public class ThreadDemo44 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程进入休眠:" + new Date());
LockSupport.parkUntil(System.currentTimeMillis() + 1000);//需加上当前系统时间
System.out.println("线程终止休眠:" + new Date());
}
}).start();
}
}
LockSupport.park()相当于wait()的升级版;LockSupport().park()使用时虽然没有Interrupt()的try catch,但仍然可以正常感知到Interrupt()方法。
重点:
使用wait(),notify(),notifyAll()注意事项:
1. 在使用以上方法的时候必须要加锁。
2. 加锁对象和 wait/notify/notifyAll 的对象必须保持一致。
3. 一组wait和notfiy/notfiyAll 必须是同一对象。
5. notifyAll只能唤醒当前对象的所有等待线程。
wait()和LockSupport.park()的 区别:
相同点:
1. 两个都可以让线程进行休眠。
2. 二者都可以参数或者不传参,并且二者线程状态也是一致的。
不同点:
- wait必须要配合synchronized一起使用(必须加锁),而 LockSupport 不允许加锁。
- wait对应的唤醒方法只能唤醒全部或随机的一个线程,而LockSupport的对应唤醒方法可以唤醒指定的线程。
Thread.sleep(0) VS Obiect.wait(0):
-
sleep()休眠的缺点是必须要有明确的休眠时间。
-
wait来自于0bject中的一个方法,而sleep是Thread的静态方法。
-
sleep(0)表示立即出发一次CPU资源的抢占,wait(0)表示永久的等待下去。
面试题1:wait()和sleep()的区别?
相同点:
1.都可以让当前线程休眠;
2.都必须要处理一个Interrupt异常。
不同点:
1. wait来自于0bject中的一个方法,而sleep是Thread的静态方法。
2. 传参不同,wait可以没有参数,而sleep必须有一个大于等于0的参数。
3. wait使用是必须加锁,sleep使用时不用加锁。
证明代码如下:
/**
* 证明wait会释放锁 sleep不会
*/
public class ThreadDemo40 {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
System.out.println("线程1:拥有了锁 lock,进入休眠状态");
// try {
// lock.wait(999999 * 1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
try {
Thread.sleep(3 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1:执行完成,释放锁 l ock");
}
}
});
t1.start();
Thread.sleep(1000);
// 主线程试图获取锁
synchronized (lock) {
System.out.println("主线程获取到锁 lock");
}
}
}
4.wait使用时会释放锁,而sleep不会释放锁。
5. wait 默认不传参的情况下会进入 WAITING状态,而sleep会进入TIME_WAITING状态。
面试题2:为什么wait会放到Object(对象)中而不是Thread(线程)中?
wait操作必须要加锁和释放锁,而锁又是属于对象级别而非线程级别(线程和锁是一对多的关系,也就是一个线程可以拥有多把锁),为了灵活起见(一个线程当中会有多把锁),就把 wait放在0bject。
面试题3:wait()为什么要加锁?
因为wait()在使用的时候要释放锁。
面试题4:wait()为什么要释放锁?
wait()是默认不传递任何值的,当不传递任何值的时候表示永久的等待,这样就会造成一把锁一直被一个线程所持有,为了避免这种问题的发生,所以wait()需要释放锁。