目录
前言
不废话直接上图
附加一个线程状态图吧不然看不懂
还是再附加一个文字吧看不懂直接往后看案例很清晰(下面这段话还回头再看)
1:stop()方法
1.1 stop()方法与java.lang.ThreadDeath异常
1.2 使用stop()释放锁给数据造成不一致的结果
2:suspend()方法
2.1 suspend()方法与resume()方法的使用
2.2 suspend()方法与resume()方法的缺点——独占
2.3 suspend()方法与resume()方法的缺点——数据不完整
3:yield()方法
4:wait方法
4.1 wait/notify机制的原理
4.2 wait()方法的基本使用
4.3 完整实现wait/notify机制
4.4 使用wait/notify机制实现list.size()等于5时的线程销毁
4.5 wait()方法:立即释放锁
5:sleep()方法
6:notify()方法
记得回头看线程的流程
stop | supend | yield | wait | sleep | notify |
停止 | 暂停 | 让步 | 等待 | 睡眠 | 通知 |
线程都死了锁就不存在了 | 不让出锁 | 让出CPU | 让出锁 | 不让出锁 | 不立即释放锁 |
1)创建一个新的线程对象后,调用它的start()方法,系统会为此线程分配CPU资源,此时线程处于runnable(可运行)状态,这是一个准备运行的阶段。如果线程抢占到CPU资源,则此线程就处于running(运行)状态。
2)runnable状态和running状态可相互切换,因为有可能线程运行一段时间后,其他高优先级的线程抢占了CPU资源,这时此线程就从running状态变成runnable状态。
线程进入runnable状态大体分为如下4种情况。
·调用sleep()方法后经过的时间超过了指定的休眠时间;
·线程成功获得了试图同步的监视器;
·线程正在等待某个通知,其他线程发出了通知;
·处于挂起状态的线程调用了resume恢复方法。
3)blocked是阻塞的意思,例如,如果遇到了一个I/O操作,此时当前线程由runnable运行状态转成blocked阻塞状态,等待I/O操作的结果。这时操作系统会把宝贵的CPU时间片分配给其他线程,当I/O操作结束后,线程由blocked状态结束,进入runnable状态,线程会继续运行后面的任务。
出现阻塞的情况大体分为如下5种。
·线程调用sleep()方法,主动放弃占用的处理器资源。
·线程调用了阻塞式I/O方法,在该方法返回前,该线程被阻塞。
·线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。
·线程等待某个通知(notify)。
·程序调用了suspend()方法将该线程挂起。此方法容易导致死锁,应尽量避免使用该方法。
4)run()方法运行结束后进入销毁阶段,整个线程执行完毕。
用stop()方法暴力停止线程
使用stop()方法可以强行停止线程,即暴力停止线程。
新建项目useStopMethodThreadTest,文件MyThread.java代码如下:
package testpackage;
public class MyThread extends Thread {
private int i = 0;
@Override
public void run() {
try {
while (true) {
i++;
System.out.println("i=" + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
文件Run.java代码如下:
package test.run;
import testpackage.MyThread;
public class Run {
public static void main(String[] args) {
try {
MyThread thread = new MyThread();
thread.start();
Thread.sleep(8000);
thread.stop();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
程序运行结果如图1-50所示。
图1-50 线程被暴力停止,stop运行图标呈灰色
由运行结果可以看出,线程被暴力停止了,这种方式就是1.11节介绍的第二种停止线程的方法——使用stop()方法强行终止线程。
stop()方法呈删除线程状态,是不再被采用的方法,原因是stop()方法容易造成业务处理的不确定性。例如,A线程执行如下业务:
增加数据1 增加数据2 增加数据3 增加数据4 增加数据5 增加数据6 增加数据7
这时在任意时机对A线程调用stop()方法,A线程并不能确定在哪里被停止了,造成数据增加得不完整。
调用stop()方法时会抛出java.lang.ThreadDeath异常,但在通常情况下,此异常不需要显式地捕捉。
创建测试用的项目runMethodUseStopMethod,文件MyThread.java代码如下:
package testpackage;
public class MyThread extends Thread {
@Override
public void run() {
try {
this.stop();
} catch (ThreadDeath e) {
System.out.println("进入了catch()方法!");
e.printStackTrace();
}
}
}
文件Run.java代码如下:
package test.run;
import testpackage.MyThread;
public class Run {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
程序运行结果如图1-51所示。
图1-51 进入catch异常
stop()方法已经是作废的方法,因为如果暴力性地强制让线程停止,则一些清理性的工作可能得不到完成,或者数据添加不完整。
对锁定的对象进行“解锁”,会导致数据得不到同步的处理,进而出现数据不一致的问题。本节将会讲解使用stop()释放锁给数据造成不一致性的结果,如果出现这样的情况,则程序处理的数据完全有可能遭到破坏,最终导致程序执行的流程是错误的,在此一定要注意。下面来看一个示例。
创建项目stopThrowLock,文件MyService.java代码如下:
package testpackage;
public class MyService {
private String username = "a";
private String passsword = "aa";
synchronized public String getUsername() {
return username;
}
synchronized public String getPasssword() {
return passsword;
}
synchronized public void printString(String username, String passsword) {
try {
this.username = username;
Thread.sleep(100000000);
this.passsword = passsword;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
调用业务方法printString()的线程代码如下:
package testpackage;
public class MyThreadA extends Thread {
private MyService object;
public MyThreadA(MyService object) {
super();
this.object = object;
}
@Override
public void run() {
object.printString("b", "bb");
}
}
输出username和passsword的线程代码如下:
package testpackage;
public class MyThreadB extends Thread {
private MyService object;
public MyThreadB(MyService object) {
super();
this.object = object;
}
@Override
public void run() {
System.out.println("username=" + object.getUsername());
System.out.println("passsword=" + object.getPasssword());
}
}
文件Run.java代码如下:
package test.run;
import testpackage.MyService;
import testpackage.MyThreadA;
import testpackage.MyThreadB;
public class Run {
public static void main(String[] args) {
try {
MyService object = new MyService();
MyThreadA threadA = new MyThreadA(object);
threadA.start();
Thread.sleep(100);
MyThreadB threadB = new MyThreadB(object);
threadB.start();
Thread.sleep(3000);
threadA.stop();
System.out.println("stop()执行后,在下方开始打印username和passsword。");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
程序运行结果如图1-52所示。
图1-52 强制停止线程造成数据不一致
由于stop()方法已经在JDK中被标明是“作废/过期”的方法,显然它在功能上具有缺陷,所以不建议在程序中使用stop()方法停止线程。
暂停线程意味着此线程还可以恢复运行,在Java多线程中,可以使用suspend()方法暂停线程,使用resume()方法来恢复线程的执行。
本节将讲述suspend()方法与resume()方法的使用。
创建测试用的项目suspend_resume_test,文件MyThread.java代码如下:
package mythread;
public class MyThread extends Thread {
private long i = 0;
public long getI() {
return i;
}
public void setI(long i) {
this.i = i;
}
@Override
public void run() {
while (true) {
i++;
}
}
}
文件Run.java代码如下:
package test.run;
import mythread.MyThread;
public class Run {
public static void main(String[] args) {
try {
MyThread thread = new MyThread();
thread.start();
Thread.sleep(5000);
// A段
thread.suspend();
System.out.println("A= " + System.currentTimeMillis() + " i="
+ thread.getI());
Thread.sleep(5000);
System.out.println("A= " + System.currentTimeMillis() + " i="
+ thread.getI());
// B段
thread.resume();
Thread.sleep(5000);
// C段
thread.suspend();
System.out.println("B= " + System.currentTimeMillis() + " i="
+ thread.getI());
Thread.sleep(5000);
System.out.println("B= " + System.currentTimeMillis() + " i="
+ thread.getI());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
程序运行结果如图1-54所示。
图1-54 暂停与恢复的测试
stop()方法用于销毁线程对象,如果想继续运行线程,则必须使用start()方法重新启动线程,而suspend()方法用于让线程不再执行任务,线程对象并不销毁,在当前所执行的代码处暂停,未来还可以恢复运行。
从控制台输出的时间上来看,线程的确被暂停了,而且可以恢复成运行状态。
如果suspend()方法与resume()方法使用不当,极易造成公共同步对象被独占,其他线程无法访问公共同步对象的结果。
创建suspend_resume_deal_lock项目,文件SynchronizedObject.java代码如下:
package testpackage;
public class SynchronizedObject {
synchronized public void printString() {
System.out.println("begin");
if (Thread.currentThread().getName().equals("a")) {
System.out.println("a线程永远 suspend了!");
Thread.currentThread().suspend();
}
System.out.println("end");
}
}
文件Run.java代码如下:
package test.run;
import testpackage.SynchronizedObject;
public class Run {
public static void main(String[] args) {
try {
final SynchronizedObject object = new SynchronizedObject();
Thread thread1 = new Thread() {
@Override
public void run() {
object.printString();
}
};
thread1.setName("a");
thread1.start();
Thread.sleep(1000);
Thread thread2 = new Thread() {
@Override
public void run() {
System.out.println("thread2启动了,但进入不了printString()方法!只打印1个begin");
System.out.println("因为printString()方法被a线程锁定并且永远suspend暂停了!");
object.printString();
}
};
thread2.start();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
程序运行结果如图1-55所示。
另外一种独占锁的情况也需要格外注意,稍有不注意,就会掉进“坑”里。创建测试用的项目suspend_resume_LockStop,类MyThread.java代码如下:
package mythread;
public class MyThread extends Thread {
private long i = 0;
@Override
public void run() {
while(true) {
i++;
}
}
}
图1-55 独占并锁死printString()方法
类Run.java代码如下:
package test.run;
import mythread.MyThread;
public class Run {
public static void main(String[] args) {
try {
MyThread thread = new MyThread();
thread.start();
Thread.sleep(1000);
thread.suspend();
System.out.println("main end!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
程序运行结果如图1-56所示。
图1-56 控制台输出main end信息
进程状态在控制台中呈红色按钮显示,说明进程并未销毁。虽然main线程销毁了,但是MyThread呈暂停状态,所以进程不会销毁。
但如果将线程类MyThread.java更改如下:
package mythread;
public class MyThread extends Thread {
private long i = 0;
@Override
public void run() {
while (true) {
i++;
System.out.println(i);
}
}
}
再次运行程序,控制台不输出main end,如图1-57所示。
图1-57 不输出main end信息
出现这种情况的原因是当程序运行到System.out.println(i)方法内部停止时,同步锁是不释放的,println()方法源代码如图1-58所示。
图1-58 锁不释放
当前PrintStream对象的println()方法一直呈“暂停”状态,并且“锁未释放”,而main()方法中的代码“System.out.println("main end!");”也需要这把锁,main线程并未销毁,造成迟迟不能输出main end。
虽然suspend()方法是过期作废的方法,但研究其过期作废的原因是很有必要的。
在使用suspend()方法与resume()方法时也容易出现线程暂停,进而导致数据不完整的情况。
创建项目suspend_resume_nosameValue,文件MyObject.java代码如下:
package myobject;
public class MyObject {
private String username = "1";
private String passsword = "11";
public void setValue(String u, String p) {
this.username = u;
if (Thread.currentThread().getName().equals("a")) {
System.out.println("停止a线程!");
Thread.currentThread().suspend();
}
this.passsword = p;
}
public void printUsernamePasssword() {
System.out.println(username + " " + passsword);
}
}
文件Run.java代码如下:
package test;
import myobject.MyObject;
public class Run {
public static void main(String[] args) throws InterruptedException {
final MyObject myobject = new MyObject();
Thread thread1 = new Thread() {
public void run() {
myobject.setValue("a", "aa");
};
};
thread1.setName("a");
thread1.start();
Thread.sleep(500);
Thread thread2 = new Thread() {
public void run() {
myobject.printUsernamePasssword();
};
};
thread2.start();
}
}
程序运行结果如图1-59所示。
图1-59 程序运行结果
程序运行结果出现值不完整的情况,所以在程序中使用suspend()方法要格外注意。
这两个方法被标识为作废过期的,想要实现对线程进行暂停与恢复的处理,可使用wait()、notify()或notifyAll()方法。
yield()方法的作用是放弃当前的CPU资源,让其他任务去占用CPU执行时间,放弃的时间不确定,有可能刚刚放弃,马上又获得CPU时间片。在本示例中可以取得运行的时间为结果,以测试yield()方法的使用效果。
创建t17项目,MyThread.java文件代码如下:
package extthread;
public class MyThread extends Thread {
@Override
public void run() {
long beginTime = System.currentTimeMillis();
int count = 0;
for (int i = 0; i < 50000000; i++) {
// Thread.yield();
count = count + (i + 1);
}
long endTime = System.currentTimeMillis();
System.out.println("用时:" + (endTime - beginTime) + "毫秒!");
}
}
文件Run.java代码如下:
package test;
import extthread.MyThread;
import extthread.MyThread;
public class Run {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
程序运行结果如图1-60所示。
将代码:
// Thread.yield();
去掉注释,再次运行,结果如图1-61所示。
wait/notify机制在生活中比比皆是,例如在就餐时就会出现,如图3-2所示。
图3-2 就餐的wait/notify机制
厨师和服务员的交互发生在“菜品传递台”上,在这期间需考虑以下几个问题。
1)厨师做完一个菜的时间未定,所以厨师将菜品放到“菜品传递台”上的时间也未定。
2)服务员取到菜的时间取决于厨师,所以服务员就有“等待”(wait)状态。
3)服务员如何取到菜呢,这取决于厨师,厨师将菜放在“菜品传递台”上,其实相当于一种通知(notify),这时服务员才可以拿到菜并交给就餐者。
在这个过程中出现了wait/notify机制。
需要说明的是,前面章节中多个线程之间也可以实现通信,原因是多个线程共同访问同一个变量,但那种通信机制不是wait/notify机制,两个线程完全是主动式地操作同一个共享变量,在花费读取时间的基础上,读到的值是不是想要的,并不能完全确定,例如,前面示例中添加了“Thread.sleep(2000);”代码,导致B线程不能退出,所以现在迫切需要引入wait/notify机制来满足上面的需求。
拥有相同锁的线程才可以实现wait/notify机制,所以后面的描述中都是假定操作同一个锁。
wait()方法是Object类的方法,它的作用是使当前执行wait()方法的线程等待,在wait()所在的代码行处暂停执行,并释放锁,直到接到通知或被中断为止。在调用wait()之前,线程必须获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。通过通知机制使某个线程继续执行wait()方法后面的代码时,对线程的选择是按照执行wait()方法的顺序确定的,并需要重新获得锁。如果调用wait()时没有持有适当的锁,则抛出IllegalMonitorStateException,它是RuntimeException的一个子类,因此不需要try-catch语句捕捉异常。
notify()方法要在同步方法或同步块中调用,即在调用前,线程必须获得锁,如果调用notify()时没有持有适当的锁,则会抛出IllegalMonitorStateException。该方法用来通知那些可能等待该锁的其他线程,如果有多个线程等待,则按照执行wait()方法的顺序对处于wait状态的线程发出一次通知(notify),并使该线程重新获取锁。需要说明的是,执行notify()方法后,当前线程不会马上释放该锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized同步区域后,当前线程才会释放锁,而呈wait状态的线程才可以获取该对象锁。当第一个获得了该对象锁的wait线程运行完毕后,它会释放该对象锁,此时如果没有再次使用notify语句,那么其他呈wait状态的线程因为没有得到通知,会继续处于wait状态。
总结:wait()方法使线程暂停运行,而notify()方法通知暂停的线程继续运行。
wait()方法的作用是使当前线程暂停运行,并释放锁。
创建测试用的Java项目,名称为test1,类Test1.java代码如下:
package test;
public class Test1 {
public static void main(String[] args) {
try {
String newString = new String("");
newString.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
程序运行结果如图3-3所示。
图3-3 出现异常
出现异常的原因是没有“对象监视器”,即没有锁。
继续创建Test2.java文件,代码如下:
package test;
public class Test2 {
public static void main(String[] args) {
try {
String lock = new String();
System.out.println("syn上面");
synchronized (lock) {
System.out.println("syn第一行");
lock.wait();
System.out.println("wait下的代码!");
}
System.out.println("syn下面的代码");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
程序运行结果如图3-4所示。
图3-4 wait()方法后面的代码不执行了
线程不能永远等待下去,那样程序就停止不前,不能继续向下运行了,如何使呈wait状态的线程继续运行呢?答案就是使用notify()方法。
创建实验用的项目,名称为test2,类MyThread1.java代码如下:
package extthread;
public class MyThread1 extends Thread {
private Object lock;
public MyThread1(Object lock) {
super();
this.lock = lock;
}
@Override
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) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
类MyThread2.java代码如下:
package extthread;
public class MyThread2 extends Thread {
private Object lock;
public MyThread2(Object lock) {
super();
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
System.out.println("开始notify time=" + System.currentTimeMillis());
lock.notify();
System.out.println("结束notify time=" + System.currentTimeMillis());
}
}
}
类Test.java代码如下:
package test;
import extthread.MyThread1;
import extthread.MyThread2;
public class Test {
public static void main(String[] args) {
try {
Object lock = new Object();
MyThread1 t1 = new MyThread1(lock);
t1.start();
Thread.sleep(3000);
MyThread2 t2 = new MyThread2(lock);
t2.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
程序运行结果如图3-5所示。
图3-5 使用wait/notify方法的示例
从程序运行结果来看,3s后线程被通知(notify)唤醒。
下面通过一个示例来演示如何使用wait()与notify()来实现前面list.size()值等于5时的线程销毁。创建新的项目wait_notify_size5,类MyList.java代码如下:
package extlist;
import java.util.ArrayList;
import java.util.List;
public class MyList {
private static List list = new ArrayList();
public static void add() {
list.add("anyString");
}
public static int size() {
return list.size();
}
}
类ThreadA.java代码如下:
package extthread;
import extlist.MyList;
public class ThreadA extends Thread {
private Object lock;
public ThreadA(Object lock) {
super();
this.lock = lock;
}
@Override
public void run() {
try {
synchronized (lock) {
if (MyList.size() != 5) {
System.out.println("wait begin "
+ System.currentTimeMillis());
lock.wait();
System.out.println("wait end "
+ System.currentTimeMillis());
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
类ThreadB.java代码如下:
package extthread;
import extlist.MyList;
public class ThreadB extends Thread {
private Object lock;
public ThreadB(Object lock) {
super();
this.lock = lock;
}
@Override
public void run() {
try {
synchronized (lock) {
for (int i = 0; i < 10; i++) {
MyList.add();
if (MyList.size() == 5) {
lock.notify();
System.out.println("已发出通知!");
}
System.out.println("添加了" + (i + 1) + "个元素!");
Thread.sleep(1000);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
类Run.java代码如下:
package test;
import extthread.ThreadA;
import extthread.ThreadB;
public class Run {
public static void main(String[] args) {
try {
Object lock = new Object();
ThreadA a = new ThreadA(lock);
a.start();
Thread.sleep(50);
ThreadB b = new ThreadB(lock);
b.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
程序运行结果如图3-6所示。
图3-6 程序运行结果
日志信息wait end在最后输出,这说明notify()方法执行后并不立即释放锁,这个知识点在后面章节会进行详细介绍。
关键字synchronized可以将任何一个Object对象作为锁来看待,而Java为每个Object都实现了wait()和notify()方法,它们必须用在被synchronized同步的Object的临界区内。通过调用wait()方法可以使处于临界区内的线程进入等待状态,同时释放被同步对象的锁,而notify操作可以唤醒一个因调用了wait操作而处于wait状态中的线程,使其进入就绪状态,被重新唤醒的线程会试图重新获得临界区的控制权,也就是锁,并继续执行临界区内wait之后的代码。如果发出notify操作时没有处于wait状态中的线程,那么该命令会被忽略。
wait()方法可以使调用该方法的线程释放锁,然后从运行状态转换成wait状态,等待被唤醒。
notify()方法按照执行wait()方法的顺序唤醒等待同一锁的“一个”线程,使其进入可运行状态,即notify()方法仅通知“一个”线程。
notifyAll()方法执行后,会按照执行wait()方法相反的顺序依次唤醒全部的线程。
执行wait()方法后,锁被立即释放。
创建实验用的项目,名称为waitReleaseLock,类Service.java代码如下:
package service;
public class Service {
public void testMethod(Object lock) {
try {
synchronized (lock) {
System.out.println("begin wait()");
lock.wait();
System.out.println(" end wait()");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
两个自定义线程类代码如图3-8所示。
图3-8 两个自定义线程类代码
运行类Test.java代码如下:
package test;
import extthread.ThreadA;
import extthread.ThreadB;
public class Test {
public static void main(String[] args) {
Object lock = new Object();
ThreadA a = new ThreadA(lock);
a.start();
ThreadB b = new ThreadB(lock);
b.start();
}
}
程序运行结果如图3-9所示。
图3-9 wait()方法自动将锁释放
sleep()方法:不释放锁
如果将wait()方法改成sleep()方法,就获得了同步的效果,因为sleep()方法不释放锁,如图3-10所示。
图3-10 sleep()方法不释放锁
执行notify()方法后,不立即释放锁,下面通过示例来验证。
创建新的项目notifyHoldLock,类MyService.java代码如下:
package service;
public class MyService {
private Object lock = new Object();
public void waitMethod() {
try {
synchronized (lock) {
System.out.println("begin wait() ThreadName=" +
Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
lock.wait();
System.out.println(" end wait() ThreadName=" +
Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void notifyMethod() {
try {
synchronized (lock) {
System.out.println("begin notify() ThreadName=" +
Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
lock.notify();
Thread.sleep(5000);
System.out.println(" end notify() ThreadName=" +
Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
类MyThreadA.java和MyThreadB.java代码如下:
package extthread;
import service.MyService;
public class MyThreadA extends Thread {
private MyService myService;
public MyThreadA(MyService myService) {
super();
this.myService = myService;
}
@Override
public void run() {
myService.waitMethod();
}
}
package extthread;
import service.MyService;
public class MyThreadB extends Thread {
private MyService myService;
public MyThreadB(MyService myService) {
super();
this.myService = myService;
}
@Override
public void run() {
myService.notifyMethod();
}
}
类Test.java代码如下:
package test;
import extthread.MyThreadA;
import extthread.MyThreadB;
import service.MyService;
public class Test {
public static void main(String[] args) throws InterruptedException {
MyService myService = new MyService();
MyThreadA a = new MyThreadA(myService);
a.start();
Thread.sleep(50);
MyThreadB b = new MyThreadB(myService);
b.start();
}
}
程序运行结果如图3-11所示。
图3-11 notify()方法执行后锁不释放
通过对控制台输出的时间的分析,可以总结出:必须执行完notify()方法所在的同步synchronized代码块后才释放锁。