3.1等待与通知机制
-
A 线程执行每一步的时间较长。
public class ThreadA extends Thread{ private MyList list; public ThreadA(MyList list){ super(); this.list = list; } @Override public void run(){ super.run(); try{ for (int i = 0; i < 10; i++) { list.add(); System.out.println(Thread.currentThread().getName() + " Add " + i); Thread.sleep(1000); } }catch (InterruptedException e){ e.printStackTrace(); } } }
-
B线程执行轮询的时间较短
public class ThreadB extends Thread{ private MyList list; public ThreadB(MyList list){ super(); this.list = list; } @Override public void run(){ super.run(); try{ while (true){ if(list.size() >= 5){ System.out.println("B Up to five."); throw new InterruptedException(); } Thread.sleep(20); } }catch (InterruptedException e){ e.printStackTrace(); } } }
线程 A 和 执行循环每轮会休息 1s,线程 B 每隔 20ms 对 size 进行访问。而线程 B 的轮询,若时间太长则无法准确捕捉 list.size的变化;若时间太短则耗费大量的 CPU 资源。
所以,使用 while 来轮询变量或者对象属性,会造成 CPU 资源的浪费;轮询间隔越短,越耗费资源。
我们使用一种称为 wait/notify 的机制来解决这个问题。
多线程共享变量,会有同步的问题,线程无法确保读取到的变量值是不是最新版本,而 wait/notify机制 恰好可以解决同步这个问题。
Java 的对象都有 wait() 方法和 notify() 方法,但需要同步加锁才能使用这个 wait/notify 机制。
-
两个线程共用一个对象监视器 lock 『共用一把锁』
一个线程中,调用对象监视器的 wait(),该线程释放锁,并且进入等待状态。
另外一个线程,在需要的时候调用对象监视器的 notify() 方法,会释放锁,并且随机唤醒等待同一共享资源的一个线程,但 notify 是等到同步块执行完成才释放锁。
-
ThreadA.java
public class ThreadA extends Thread{ private Object lock; public ThreadA(Object lock){ super(); this.lock = lock; } @Override public void run(){ super.run(); synchronized (lock){ System.out.println("start."); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("end"); } } }
-
ThreadB.java
public class ThreadB extends Thread{ private Object lock; public ThreadB(Object lock){ super(); this.lock = lock; } @Override public void run(){ super.run(); synchronized (lock){ for (int i = 0; i < 7; i++) { System.out.println(i); try { Thread.sleep(1000); }catch (InterruptedException e){ e.printStackTrace(); } if (i == 4){ lock.notify(); } } } } }
-
Run.java
public class Run { public static void main(String[] args) { Object lock = new Object(); ThreadA A = new ThreadA(lock); A.setName("A"); A.start(); ThreadB B = new ThreadB(lock); B.setName("B"); B.start(); } }
可以看到,在 i = 4 时调用notify( ),并不会马上释放锁。
-
-
线程状态
-
NEW 新建
-
RUNNABLE 就绪状态
线程可以 CPU 调度,若该线程抢占到了 CPU 资源,那么就可以进入 RUNNING 状态。
进入 Running 状态的情况
- Sleep() 休眠已结束。
- 线程调用的阻塞 IO 已返回,阻塞方法执行完毕。
- 线程成功获得了锁之后。
- 正在等待通知的线程,被其他线程通知,并且有最高的优先级来执行。
- 处于挂起状态的线程调用了 resume 方法。
-
BLOCKED 阻塞
比如遇到 IO 操作,当前线程进入阻塞状态,把 CPU 时间片让给其他线程。Blocked 状态结束后,线程进入Runnable 状态。
进入 Blocked 状态的情况
- 线程调用 Sleep() ,主动放弃占用 CPU 资源。
- 线程调用了 阻塞式 IO,在该方法被返回前,该线程被阻塞。
- 线程试图获得同步监视器,但该同步监视器正在被其他线程持有。(轮不到获得同步锁)
- 多个线程同时收到可触发的通知,优先级高的先获得锁得以进入runable 状态,其他线程处于 blocked 状态。
- 某个线程调用了 其他线程的 suspend 方法将其他线程挂起,被挂起者进入 Blocked 状态。该方法容易产生死锁。
-
WAITING 等待
该线程由其他线程的 notify 来唤醒。
-
TIMED_WAITING 有限时间的等待。
到达一定时间后自我唤醒或者受到他人唤醒。
-
TERMINATED 终止
run() 方法执行完毕 或者 main 方法完成之后,线程进入terminated 状态。
-
-
锁对象
每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。
-
wait/notify 内容补充
-
wait() 会释放锁
-
notify() 不释放锁
-
sleep() 不释放锁
-
wait() 之后的线程,可以被 interrupt() 打断,并且释放锁。
-
通知过早
若通知过早,会打乱程序运行逻辑。
public class MyRun { private String lock = new String(""); public Runnable runnableA = new Runnable() { @Override public void run() { try{ synchronized (lock){ System.out.println("Start wait"); lock.wait(); System.out.println("End wait"); } }catch (Exception e){ } } }; public Runnable runnableB = new Runnable() { @Override public void run() { synchronized (lock){ System.out.println("Start notify"); lock.notify(); System.out.println("End notify"); } } }; }
过早通知如下
public static void main(String[] args) { MyRun run = new MyRun(); Thread B = new Thread(run.runnableB); B.setName("B"); Thread A = new Thread(run.runnableA); A.setName("A"); B.start(); //先通知 A.start(); //再等待 }
正常顺序启动
public static void main(String[] args) { MyRun run = new MyRun(); Thread B = new Thread(run.runnableB); B.setName("B"); Thread A = new Thread(run.runnableA); A.setName("A"); A.start(); //先等待 B.start(); //再通知 }
-
wait 的条件发生变化会导致程序逻辑的混乱
- wait() 与 Thread.sleep()的区别 wait() 会释放同步锁,而Thread.sleep() 不会释放锁。
-