TODO:
1、为什么通信的方法wait()、nofity()、notifyAll()被定义在Object类里,而sleep被定义在Thread类里?
2、用三种方式实现生产者模式
3、join、sleep和wait期间,线程的状态分别是什么?为什么?
wait,notify,notifyAll()
1、作用、用法
阻塞阶段:
使用wait()后进入阻塞阶段,以下四种情况之一发生,才会被唤醒
- 另一个线程调用了这个对象的notify()方法且刚好唤醒本线程(因为notify方法唤醒哪个线程是不确定的)
- 另一个线程调用了notifyAll()方法
- wait(long timeout),等待时间超过了规定时间,如果传入0表示永久等待
- 线程自身调用了interrupt(),因为wait()是会响应中断的
唤醒阶段
遇到中断
//普通情况
package com.company;
public class WaitAndNotify {
public static Object object = new Object();
static class WaitRun implements Runnable {
@Override
public void run() {
synchronized (object) {
System.out.println(System.currentTimeMillis() + ": " + Thread.currentThread().getName() + "开始执行了");
//wait后丢掉锁
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//如果再次获得锁才会执行接下来的代码
System.out.println(System.currentTimeMillis() + ": " + Thread.currentThread().getName() + "又重新获取到了锁");
}
}
}
static class NotifuRun implements Runnable {
@Override
public void run() {
synchronized (object) {
object.notify();
System.out.println(Thread.currentThread().getName() + "调用了notify(),");
//为了看使用了notify后是立即释放锁,还是等执行完成后 释放锁
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new WaitRun());
Thread t2 = new Thread(new NotifuRun());
t1.start();
//为了让t1先使用到了wait进入到阻塞态
Thread.sleep(100);
t2.start();
}
}

notify和notifyAll()
/**
* 1、看notify()和notifyAll()的区别
* 2、观察notify()在所有线程都进入阻塞态前就执行的结果
*/
public class WaitNotifyAll implements Runnable {
private static final Object RESOURCE = new Object();
@Override
public void run() {
synchronized (RESOURCE) {
System.out.println(Thread.currentThread().getName() + " get RESOURCE lock");
try {
System.out.println(Thread.currentThread().getName()+" waits");
RESOURCE.wait();
System.out.println(Thread.currentThread().getName()+" 'reget RESOURCE lock");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new WaitNotifyAll());
Thread t2 = new Thread(new WaitNotifyAll());
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (RESOURCE){
RESOURCE.notifyAll();
// RESOURCE.notify();
System.out.println("Thread 3 notified");
}
}
});
t1.start();
t2.start();
//确保t1,t2都已经执行
Thread.sleep(200);
t3.start();
}
}
使用notifyAll()会唤醒所有使用RESOURCE锁的线程

只释放当前monitor
/**
* 证明wait只释放当前的那把锁
*/
public class WaitNotifyReleaseOwnMonitor {
private static final Object ResourceA = new Object();
private static final Object ResourceB = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(()->{
synchronized (ResourceA) {
System.out.println("T1 get ResourceA lcok");
synchronized (ResourceB) {
System.out.println("T1 get ResourceB lock");
try {
System.out.println("T1 release ResourceA lock");
ResourceA.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
);
Thread t2 = new Thread(()->{
//让t1先运行起来,或得到A、B两把锁
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (ResourceA) {
System.out.println("T2 get ResourceA lock");
System.out.println("T2 try to get ResourceB lock");
synchronized (ResourceB) {
System.out.println("T2 get ResourceB lock");
}
}
});
t1.start();
t2.start();
}
}
从结果可以看出来,某个对象只释放当前对应的monitor锁,T2没有获得到ResourceB的锁。

sleep()
1、不释放锁,包括synchronized和lock ,这是与wait最大的不同点
/**
* 考查sleep是否释放synchrd的monitor锁
*/
public class SleepDontReleaseMonitor implements Runnable{
public static void main(String[] args) {
SleepDontReleaseMonitor run = new SleepDontReleaseMonitor();
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t1.start();
t2.start();
}
@Override
public void run() {
syn();
}
private synchronized void syn() {
System.out.println(Thread.currentThread().getName()+" starts");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" ends");
}
}
可以看出来,两个线程串行执行,sleep并不释放锁

对于lock锁
/**
* 考查sleep是否释放lock锁(不会)
*/
public class SleepDontReleaseLock implements Runnable{
private static final Lock lock = new ReentrantLock();
@Override
public void run() {
lock.lock();
System.out.println(Thread.currentThread().getName()+" 获得了锁");
try {
Thread.sleep(5000L);
System.out.println(Thread.currentThread().getName()+" sleep结束,苏醒");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
SleepDontReleaseMonitor run = new SleepDontReleaseMonitor();
new Thread(run).start();
new Thread(run).start();
}
}
结果和上一个是一致的。

2、sleep方法响应中断
(1) 抛出InterruptedException
(2) 清除中断状态
/**
* 每过1秒钟输出当前时间,被中断,观察
* Thread.sleep()
* TimeUnit.SECONDS.sleep()
*/
public class SleepInterrupted implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(new Date());
try {
// TimeUnit.MINUTES.sleep(1);
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
System.out.println("线程被中断了");
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new SleepInterrupted());
t1.start();
Thread.sleep(3500);
t1.interrupt();
Thread.sleep(3500);
t1.interrupt();
}
}

- (作用)让线程进入waiting状态,并且不占用cpu资源
- (特点)不释放锁
- (中断)休眠期间可以响应中断,会抛出InterruptedException并清除中断状态
join
1、由于有新的线程加入我们,所以我们要等其执行完再出发 尝试用main等待thread0、1执行完毕,注意是谁等谁(主线程等子线程)
普通用法
/**
* 描述: 演示join,注意语句输出顺序,会变化。
*/
public class joinNormal {
static class WaitRunnable implements Runnable {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"执行完毕");
}
}
public static void main(String[] args) throws Exception{
Thread t1 = new Thread(new WaitRunnable());
Thread t2 = new Thread(new WaitRunnable());
t1.start();
t2.start();
System.out.println("开始等待子线程运行完毕");
t1.join();
t2.join();
System.out.println("所有线程都已执行完毕");
}
}

如果注释掉两行join,主线程和子线程是并行执行的,没有等待子线程先执行完毕.

2、主线程在等待子线程join的过程中,如果被中断,那么就不会再等待子线程执行完成了,而是catch到这个中断。
public class joinInterrupt {
public static void main(String[] args) {
Thread mainThread = Thread.currentThread();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
mainThread.interrupt();
Thread.sleep(5000);
System.out.println("Thread1 finished.");
} catch (InterruptedException e) {
System.out.println("子线程中断");
}
}
});
thread1.start();
System.out.println("等待子线程运行完毕");
try {
thread1.join();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+"主线程中断了");
//thread1.interrupt();
}
System.out.println("子线程已运行完毕");
}
}
可以看出,主线程已经运行结束了,子线程才运行结束。这样就会导致主线程和子线程的不同步,如果要处理的话就应该使用thread1.interrupt();让子线程也停止下来。

3、在等待子线程join的过程中,主线程是什么状态(Waiting)
public class joinMainState {
public static void main(String[] args) throws InterruptedException {
Thread mainThread = Thread.currentThread();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
//查看的是主线程的状态!
System.out.println(mainThread.getState());
System.out.println("Thread-0运行结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
System.out.println("等待子线程运行完毕");
thread.join();
System.out.println("子线程运行完毕");
}
}
yield
作用:释放自己的cpu时间片,但线程的状态依然是Runnable
定位:JVM不保证遵循(意思是使用了yield也不一定会释放cpu时间片)
与sleep区别:是否随时可能再次被调度(yield会)
开发中不常用,不建议使用