轮询快速到底
线程本身是操作系统中独立的个体,但是线程与线程之间不是独立的个体,因为它们彼此之间要相互通信和协作。
想像一个场景,A 线程做 int 型变量 i 的累加操作,B 线程等待 i 到了 10000 就打印出i,怎么处理?一个办法就是,B 线程 while(i == 10000),这样两个线程之间就有了通信,B 线程不断通过轮训来检测 i == 10000 这个条件。
这样可以实现我们的需求,但是也带来了问题:CPU 把资源浪费了 B 线程的轮询操作上,因为 while 操作并不释放 CPU 资源,导致了 CPU 会一直在这个线程中做判断操作。如果可以把这些轮询的时间释放出来,给别的线程用,就好了。
wait/notify
在 Object 对象中有三个方法 wait()、notify()、notifyAll(),既然是 Object 中的方法,那每个对象自然都是有的。如果不接触多线程的话,这几个方法是不太常见的。下面看一下前两个方法:
1、wait()
wait() 的作用是使当前执行代码的线程进行等待,将当前线程进入阻塞状态,并且 wait() 所在的代码处停止执行,直到接到通知或被中断。在调用 wait() 之前,线程必须获得该对象的锁,因此只能在同步方法/同步代码块中调用 wait() 方法。
2、notify()
notify() 的作用是,如果有多个线程等待,那么线程规划器随机挑选出一个 wait 的线程,对其发出通知 notify(),并使它等待获取该对象的对象锁。注意"等待获取该对象的对象锁",这意味着,即使收到了通知,wait 的线程也不会马上获取对象锁,必须等待执行 notify() 方法的线程释放锁才可以。和 wait() 一样,notify() 也要在同步方法/同步代码块中调用。
总结起来就是,wait() 使线程停止运行,notify() 使停止运行的线程继续运行。
wait()/notify() 使用示例
看一个 Demo
public class MyThread extends Thread
{
private Object lock;
public MyThread(Object lock)
{
this.lock = lock;
}
public void run()
{
try
{
synchronized (lock)
{
System.out.println("开始------wait time = " + System.currentTimeMillis());
lock.wait();
System.out.println("结束------wait time = " + System.currentTimeMillis());
}
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
public class MyThread_02 extends Thread
{
private Object lock;
public MyThread_02(Object lock)
{
this.lock = lock;
}
public void run()
{
synchronized (lock)
{
System.out.println("开始------notify time = " + System.currentTimeMillis());
lock.notify();
System.out.println("结束------notify time = " + System.currentTimeMillis());
}
}
}
public static void main(String[] args) throws Exception
{
Object lock = new Object();
MyThread mt = new MyThread(lock);
mt.start();
Thread.sleep(3000);
MyThread_02 mt_02 = new MyThread_02(lock);
mt_02.start();
}
运行结果
开始------wait time = 1443931599021
开始------notify time = 1443931602024 // 从这个时间来看很明显上面执行 lock.wait() 方法的线程已经停止运行了(已经过了 3 毫秒了)
结束------notify time = 1443931602024
结束------wait time = 1443931602024
解释:虽然上面执行了 lock.notify() 方法,但是一直等到执行 lock.notify() 方法的线程执行完 synchronized 代码块之后释放了对象锁,上面等待的线程才会拿到对象锁继续执行。
wait() 方法可以使执行该方法的线程释放共享资源的锁,然后从运行状态退出,进入等待队列,直到再次被唤醒。(其实就是锁被拿走了执行不了了)
notify() 方法可以随机唤醒等待队列中等待同一共享资源的一个线程,并使得该线程退出等待状态,进入可运行状态
notifyAll() 方法可以使所有正在等待队列中等待同一共享资源的全部线程从等待状态退出,进入可运行状态
最后,如果 wait() 方法和 notify()/notifyAll() 方法不在同步方法/同步代码块中被调用,那么虚拟机会抛出 java.lang.IllegalMonitorStateException,注意一下。
wait() 释放锁以及 notify() 不释放锁
多线程的学习中,任何地方都要关注"锁",wait() 和 notify()也是这样。wait() 方法是释放锁的,写一个例子来证明一下:
public class ThreadTest
{
public void testMethod(Object lock)
{
try
{
synchronized (lock)
{
System.out.println(Thread.currentThread().getName() + " 开始等待");
lock.wait();
System.out.println(Thread.currentThread().getName() + " 结束等待");
}
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
public class MyThread extends Thread
{
private Object lock;
public MyThread(Object lock)
{
this.lock = lock;
}
public void run()
{
ThreadTest td = new ThreadTest();
td.testMethod(lock);
}
}
public static void main(String[] args)
{
Object lock = new Object();
MyThread mt0 = new MyThread(lock);
MyThread mt1 = new MyThread(lock);
mt0.start();
mt1.start();
}
运行结果
Thread-0 开始等待
Thread-1 开始等待
如果 wait() 方法不释放锁,那么 Thread-1 根本不会进入同步代码块打印的,所以,证明完毕。
接下来证明一下 notify() 方法不释放锁的结论:
public class ThreadTest
{
public void testMethod(Object lock)
{
try
{
synchronized (lock)
{
System.out.println("开始等待, ThreadName = " + Thread.currentThread().getName());
lock.wait();
System.out.println("结束等待, ThreadName = " + Thread.currentThread().getName());
}
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
public void synNotifyMethod(Object lock)
{
try
{
synchronized (lock)
{
System.out.println("开始 notify, ThreadName = " + Thread.currentThread().getName());
lock.notify();
Thread.sleep(5000);
System.out.println("结束 notify, ThreadName = " + Thread.currentThread().getName());
}
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
public class MyThread extends Thread
{
private Object lock;
public MyThread(Object lock)
{
this.lock = lock;
}
public void run()
{
ThreadTest td = new ThreadTest();
td.testMethod(lock);
}
}
public class MyThread_02 extends Thread
{
private Object lock;
public MyThread_02(Object lock)
{
this.lock = lock;
}
public void run()
{
ThreadTest td = new ThreadTest();
td.synNotifyMethod(lock);
}
}
public static void main(String[] args) throws Exception
{
Object lock = new Object();
MyThread mt0 = new MyThread(lock);
mt0.start();
MyThread_02 mt1 = new MyThread_02(lock);
mt1.start();
MyThread_02 mt2 = new MyThread_02(lock);
mt2.start();
}
运行结果
1 开始等待, ThreadName = Thread-0
2 开始 notify(), ThreadName = Thread-1
3 结束 notify(), ThreadName = Thread-1
4 开始 notify(), ThreadName = Thread-2
5 结束 notify(), ThreadName = Thread-2
6 结束等待, ThreadName = Thread-0 // 这一步是有可能在第三步之后之后就拿到锁的
如果 notify() 方法释放锁,那么在 Thread-1 调用 notify() 方法后 Thread.sleep(5000) 必定应该有其他线程可以进入同步代码块了,但是实际上没有,必须等到Thread-1 把代码执行完。所以,证明完毕。
interrupt() 打断 wait()
之前有说过,interrupt() 方法的作用不是中断线程,而是在线程阻塞的时候给线程一个中断标识,表示该线程中断。wait() 就是"阻塞的一种场景",看一下用 interrupt() 打断 wait() 的例子:
public class ThreadTest
{
Object lock;
public ThreadTest(Object lock)
{
this.lock = lock;
}
public void show()
{
try
{
synchronized (lock)
{
System.out.println("Begin wait() " + Thread.currentThread().getName());
lock.wait();
System.out.println("End wait() " + Thread.currentThread().getName());
}
} catch (InterruptedException e)
{
System.out.println("wait()被interrupt()打断了!");
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception
{
Object lock = new Object();
MyThread myThread = new MyThread(lock);
myThread.start();
Thread.sleep(5000); // main 线程睡了 5 秒
myThread.interrupt();
}
}
class MyThread extends Thread
{
Object lock;
public MyThread(Object lock)
{
this.lock = lock;
}
@Override
public void run()
{
ThreadTest threadTest = new ThreadTest(lock);
threadTest.show();
}
}
看一下运行结果
Begin wait() Thread-0
wait()被interrupt()打断了! // 在打印这个之前暂停了 5 秒
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:503)
at com.array.ThreadTest.show(ThreadTest.java:20)
at com.array.MyThread.run(ThreadTest.java:56)
如果没有上面的 myThread.interupt(); 方法,那么五秒之后 myThread 还是处于“阻塞”的状态,那么整个程序还是卡在那里,红点还是亮着。换句话说,myThread.interupt(); 的作用是将处于“阻塞”状态的线程进行中断
notifyAll() 唤醒所有线程
利用 Object 对象的 notifyAll() 方法可以唤醒处于同一监视器下的所有处于 wait 的线程,看一个 Demo:
public class ThreadTest
{
public void testMethod(Object lock)
{
try
{
synchronized (lock)
{
System.out.println("Begin wait(), ThreadName = " + Thread.currentThread().getName());
lock.wait();
System.out.println("End wait(), ThreadName = " + Thread.currentThread().getName());
}
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
public class MyThread extends Thread
{
private Object lock;
public MyThread(Object lock)
{
this.lock = lock;
}
public void run()
{
ThreadTest td = new ThreadTest();
td.testMethod(lock);
}
}
public class MyThread_2 extends Thread
{
private Object lock;
public MyThread_2(Object lock)
{
this.lock = lock;
}
public void run()
{
synchronized (lock)
{
lock.notifyAll();
}
}
}
public static void main(String[] args) throws Exception
{
Object lock = new Object();
MyThread mt0 = new MyThread(lock);
MyThread mt1 = new MyThread(lock);
MyThread mt2 = new MyThread(lock);
mt0.start();
mt1.start();
mt2.start();
Thread.sleep(1000);
MyThread_2 mt3 = new MyThread_2(lock);
mt3.start();
}
运行结果
Begin wait(), ThreadName = Thread-0
Begin wait(), ThreadName = Thread-2
Begin wait(), ThreadName = Thread-1
End wait(), ThreadName = Thread-1
End wait(), ThreadName = Thread-2
End wait(), ThreadName = Thread-0
当然,唤醒的顺序不重要,因为 notifyAll() 把处于同一资源下 wait 的线程全部唤醒,至于唤醒的顺序,就和线程启动的顺序一样,是虚拟机随机的。