线程是一个单独程序流程。多线程是指一个程序可以同时运行多个任务,每个任务由一个单独的线程来完成。也就是说,多个线程可以同时在一个程序中运行,并且每一个线程完成不同的任务。程序可以通过控制线程来控制程序的运行,例如线程的等待、休眠、唤起线程等。本章将向读者介绍线程的机制、如何操作和使用线程以及多线程编程。
1. 线程的基本知识
线程是程序运行的基本单位,一个程序中可以同时运行多个线程。如果程序被设置为多线程, 可以提高程序运行的效率和处理速度。 Java中线程的实现通常有两种方法: 派生 Thread类和实现 Runnable接口。本节主要讲述线程的概念和创建线程的方法。
public class OnlyThread { public static void main(String args[]) { run(); // 调用静态run()方法 } /** * 实现run()方法 */ public static void run() { // 循环计算输出的*数目 for (int count = 1, row = 1; row < 10; row++, count++) { for (int i = 0; i < count; i++) { // 循环输出指定的count数目的* System.out.print('*'); } System.out.println(); } } }
在 Java程序中,有两种方法创建线程:
一是对 Thread 类进行派生并覆盖 run方法;
二是通过实现 runnable接口创建。
在程序中创建新的线程的方法之一是继承 Thread 类, 并通过 Thread子类声明线程对象。继承Thread 类并覆盖 Thread类的 run 方法完成线程类的声明, 通过new创建派生线程类的线程对象。run 中的代码实现了线程的行为。
java.lang.Thread 类是一个通用的线程类,由于默认情况下 run 方法是空的,直接通过 Thread类实例化的线程对象不能完成任何事,所以可以通过派生 Thread 类,并用具体程序代码覆盖Thread 类中的 run 方法,实现具有各种不同功能的线程类。
1) Thread 创建线程步骤:
(1)创建一个新的线程类,继承 Thread 类并覆盖 Thread 类的 run()方法。class ThreadType extends Thread{ public void run(){ …… } }(2)创建一个线程类的对象,创建方法与一般对象的创建相同,使用关键字new完成。
ThreadType tt = new ThreadType();(3)启动新线程对象,调用 start()方法。
tt.start();(4)线程自己调用 run()方法。
void run();
2) Thread创建一个线程
下面是通过Thread创建线程的例子:产生一个新的线程
class ThreadDemo1 extends Thread { ThreadDemo1() { } // 声明ThreadDemo1带参数的构造方法 ThreadDemo1(String szName) { super(szName); } // 重载run函数 public void run() { for (int count = 1, row = 1; row < 10; row++, count++) { for (int i = 0; i < count; i++) {// 循环输出指定的count数目的* System.out.print('*'); } System.out.println(); } } public static void main(String argv[]) { ThreadDemo1 td = new ThreadDemo1(); // 创建,并初始化ThreadDemo1类型对象td td.start(); // 调用start()方法执行一个新的线程 } }
3) Thread创建多个线程
//文件:程序10.3 ThreadDemo2.java 描述:产生三个新的线程public class ThreadDemo2 extends Thread { // 声明无参数,空构造方法 ThreadDemo2() { } // 声明带有字符串参数的构造方法 ThreadDemo2(String szName) { super(szName); // 调用父类的构造方法 } // 重载run函数 public void run() { for (int count = 1, row = 1; row < 10; row++, count++) { for (int i = 0; i < count; i++) {// 循环输出指定的count数目的* System.out.print('*'); } System.out.println(); } } public static void main(String argv[]) { ThreadDemo2 td1 = new ThreadDemo2(); // 创建,并初始化ThreadDemo2类型对象td1 ThreadDemo2 td2 = new ThreadDemo2(); // 创建,并初始化ThreadDemo2类型对象td2 ThreadDemo2 td3 = new ThreadDemo2(); // 创建,并初始化ThreadDemo2类型对象td3 td1.start(); // 启动线程td1 td2.start(); // 启动线程td2 td3.start(); // 启动线程td3 } }
创建了 3 个线程 td1、td2、td3,它们分别执行自己的 run方法。在实际中运行的结果并不是想要的直角三角形, 而是一些乱七八糟的 “*” 行,长短并没有一定的规律,这是因为线程并没有按照程序中调用的顺序来执行, 而是产生了多个线程赛跑现象。 运行结果:
通过实现 Runnable 接口的方法是创建线程类的第二种方法。利用实现 Runnable 接口来创建线程的方法可以解决 Java 语言不支持的多重继承问题。 Runnable 接口提供了 run()方法的原型,因此创建新的线程类时,只要实现此接口,即只要特定的程序代码实现Runnable接口中的 run()方法,就可完成新线程类的运行。
扩展Thread类创建线程的方式,适合编写简单的应用程序代码,而实现Runnable接口创建线程,能够避免Java单继承的局限,适合同一代码的多线程处理同一资源的情况,代码具有良好的一致性,是更符合面向对象思想的设计方式。
1) Runnable 创建线程步骤
(1)创建一个实现 Runnable 接口的类,并且在这个类中重写 run 方法。class ThreadType implements Runnable{ public void run(){ …… } }(2)使用关键字 new新建一个 ThreadType 的实例。
Runnable rb = new ThreadType ();(3)通过 Runnable 的实例创建一个线程对象,在创建线程对象时,调用的构造函数是 new Thread(ThreadType),它用 ThreadType 中实现的 run()方法作为新线程对象的 run()方法。
Thread td = new Thread(rb);(4)通过调用 ThreadType 对象的 start()方法启动线程运行。
td.start();
2) Runnable 创建线程
class ThreadDemo3 implements Runnable { // 重载run函数 public void run() { for (int count = 1, row = 1; row < 10; row++, count++){ // 循环计算输出的*数目 for (int i = 0; i < count; i++){ // 循环输出指定的count数目的* System.out.print('*'); } System.out.println(); } } public static void main(String argv[]) { Runnable rb = new ThreadDemo3(); // 创建,并初始化ThreadDemo3对象rb Thread td = new Thread(rb); // 通过Thread创建线程 td.start(); // 启动线程td } }
2. 线程的状态
线程的整个周期由线程创建、可运行状态、不可运行状态和退出等部分组成,这些状态之间的转化是通过线程提供的一些方法完成的。
通过 new第一次创建线程时,线程位于创建状态,这时不能运行线程,只能等待进一步的方法调用改变其状态。然后,线程通过调用 start方法启动
线程,并进入可执行状态,或者调用方法 stop进入退出状态。当程序位于退出状态时,线程已经结束执行,这是线程的最后一个状态,并且不能转化到其他状态。当程序的所有线程位于退出状态时,程序会强行终止。当线程位于可执行状态时,在一个特定的时间点上,每一个系统处理器只能运行一个线程。 此时如果线程被挂起,执行就会被中断或者进入休眠状态,那么线程将进入不可执行状态,并且不可执行状态可以通过 resume、notify等方法返回到可执行状态。表10-1列举了线程状态转换的函数。
线程状态转换函数:
方法 描述 有效状态 目的状态
start() 开始执行一个线程 New Runnable
stop() 结束执行一个线程 New或Runnable Done
sleep(long) 暂停一段时间,这个时间为给定的毫秒 Runnable NonRunnable
sleep(long,int) 暂停片刻,可以精确到纳秒 Runnable NonRunnable
suspend() 挂起执行 Runnable NonRunnable
resume() 恢复执行 NonRunnable Runnable
yield() 明确放弃执行 Runnable Runnable
wait() 进入阻塞状态 Runnable NonRunnable
notify() 阻塞状态解除 NonRunnable Runnable
注意:stop()、suspend()和 resume()方法现在已经不提倡使用,这些方法在虚拟机中可能引起“死锁”现象。suspend()和 resume()方法的替代方法是 wait()和 sleep()。线程的退出通常采用自然终止的方法,建议不要人工调用 stop()方法。
1)派生线程类 Thread 的子类,并在子类中重写 run()方法:Thread 子类的实例对象是一个线程对象,并且该线程有专门定制的线程 run()方法,启动线程后就执行子类中重写的 run()方法。
‰ 2)实现 Runnable 接口并重新定义 run()方法:先定义一个实现 Runnable()接口的类,在该类中定义 run()方法,然后创建新的线程类对象,并以该对象作为 Thread 类构造方法的参数创建一个线程。
注意:调用线程的 run()方法是通过启动线程的start()方法来实现的。 因为线程在调用start()方法之后,系统会自动调用 run()方法。与一般方法调用不同的地方在于一般方法调用另外一个方法后,必须等被调用的方法执行完毕才能返回,而线程的 start()方法被调用之后,系统会得知线程准备完毕并且可以执行run()方法,start()方法就返回了,start()方法不会等待run()方法执行完毕。
(3)线程对I/O操作的完成。
2.线程进入不可执行状态线程阻塞的例子。
public class ThreadSleep { public static void main(String[ ] args) { SubThread st = new SubThread("SubThread"); //创建,并初始化SubThread 对象st st.start(); //启动线程st } } class SubThread extends Thread{ SubThread(){} //声明,实现SubThread无参数构造方法 //声明,实现SubThread带字符串参数构造方法 SubThread(String Name) { super(Name); //调用父类的构造方法 } //重载run函数 public void run() { for (int count = 1,row = 1; row < 10; row++,count++) //循环计算输出的*数目 { for (int i = 0; i < count; i++) //循环输出指定的count数目的* { System.out.print('*'); //输出* } try //try-catch块,用于捕获异常 { Thread.sleep(1000); //线程休眠1秒钟 System.out.print("\t wait........"); } catch (InterruptedException e) //捕获异常InterruptedException { e.printStackTrace(); //异常抛出信息 } System.out.println(); //输出换行符 } } }程序ThreadSleep 中,每输出一行*就要休息1 秒钟。当执行 sleep()语句后, 线程进入不可执行状态等待1 秒钟之后,线程 st 会自动苏醒并继续执行。由于 sleep
方法抛出 InterruptedException异常, 所以在调用时必须捕获异常。
该方法用于测试线程是否处于活动状态。活动状态是指线程已经启动(调用 start方法)且尚未退出所处的状态,包括可运行状态和不可运行状态。可以通过该方法解决程序 10.3中的问题,先判断第一个线程是否已经终止,如果终止再来调用第二个线程。这里提供两种方法:
第一种方法是不断查询第一个线程是否已经终止,如果没有,则让主线程睡眠一直到它终止即“while/isAlive/sleep”,格式如下。
线程1.start(); while(线程 1.isAlive()) { Thread.sleep(休眠时间); } 线程2.start();第二种是利用 join()方法。
package Test; class WaitThreadStop extends Thread { // 声明,并实现WaitThreadStop无参数构造方法 WaitThreadStop() { } // 声明,并实现带有一个字符串参数的构造方法 WaitThreadStop(String szName) { super(szName); // 调用父类的构造方法 } // 重载run函数 public void run() { for (int count = 1, row = 1; row < 10; row++, count++) { for (int i = 0; i < count; i++) { System.out.print('*'); // 输出* } System.out.println(); // 输出换行符 } } } public class WaitThreadStopMain { public static void main(String argv[ ]){ WaitThreadStopMain test = new WaitThreadStopMain(); //创建,初始化WaitThreadStopMain对象test test.Method1(); //调用Method1方法 //test.Method2(); } // 第一种方法:while/isAlive/sleep public void Method1() { WaitThreadStop th1 = new WaitThreadStop(); // 创建,并初始化WaitThreadStop对象th1 WaitThreadStop th2 = new WaitThreadStop(); // 创建,并初始化WaitThreadStop对象th2 // 执行第一个线程 th1.start(); // 查询第一个线程的状态 while (th1.isAlive()) { try { Thread.sleep(100); // 休眠100毫秒 } catch (InterruptedException e) { e.printStackTrace(); // 异常信息输出 } } // 当第一个线程终止后,运行第二个线程 th2.start(); // 启动线程th2 } // 第二种方法,使用join方法实现等待其他线程结束 public void Method2() { WaitThreadStop th1 = new WaitThreadStop(); // 创建, 并初始化WaitThreadStop对象th1 WaitThreadStop th2 = new WaitThreadStop(); // 创建,并初始化WaitThreadStop对象th2 // 执行第一个线程 th1.start(); try { th1.join(); // th1调用join 方法 } catch (InterruptedException e) { e.printStackTrace(); // 异常信息输出 } // 执行第二个线程 th2.start(); } }
3. 线程调度
在 Java 中,CPU的使用通常是抢占式调度模式不需要时间片分配进程。抢占式调度模式是指许多线程同时处于可运行状态,但只有一个线程正在运行。当线程一直运行直到结束,或者进入不可运行状态,或者具有更高优先级的线程变为可运行状态,它将会让出 CPU。线程与优先级相关的方法如下:
public final void setPriority(int newPriority) 设置线程的优先级为 newPriority :
newPriority 的值必须在 MIN_PRIORITY 到MAX_PRIORITY范围内,通常它们的值分别是1和10。目前Windows系统只支持3个级别的优先级, 它们分别是Thread.MAX_PRIORITY、 Thread.MIN_PRIORITY和Thread.NORM_PRIORITY。
public final int getPriority() 获得当前线程的优先级。
线程优先级的例子:
class InheritThread extends Thread { //自定义线程的run()方法 public void run(){ System.out.println("InheritThread is running…"); //输出字符串信息 for(int i=0;i<10;i++){ System.out.println(" InheritThread: i="+i); //输出信息 try{ Thread.sleep((int)Math.random()*1000); //线程休眠 } catch(InterruptedException e) //捕获异常 {} } } }通过Runnable接口创建的另外一个线程 :
class RunnableThread implements Runnable { // 自定义线程的run()方法 public void run() { System.out.println("RunnableThread is running…"); // 输出字符串信息 for (int i = 0; i < 10; i++) { System.out.println("RunnableThread : i=" + i); // 输出i try { Thread.sleep((int) Math.random() * 1000); // 线程休眠 } catch (InterruptedException e) { // 捕获异常 } } } } public class ThreadPriority { public static void main(String args[]) { // 用Thread类的子类创建线程 InheritThread itd = new InheritThread(); // 用Runnable接口类的对象创建线程 Thread rtd = new Thread(new RunnableThread()); itd.setPriority(5); // 设置myThread1的优先级5 rtd.setPriority(5); // 设置myThread2的优先级5 itd.start(); // 启动线程itd rtd.start(); // 启动线程rtd } }
在程序ThreadPriority.java中,线程 rtd 和 itd 具有相同的优先级,所以它们交互占用 CPU,宏观上处于并行运行状态。结果如图3.
重新设定优先级:
itd.setPriority(1); //设置myThread1的优先级1
图3相同优先级 图2 不同的优先级
从运行结构可以看出程序ThreadPriority.java修改后,由于设置了线程itd和 rtd 的优先级,并且 rtd的优先级较高,基本上是 rtd都优先抢占 CPU资源。
4. 线程同步
//文件:程序ThreadNoSynchronized.java 描述:多线程不同步的原因 class ShareData { public static String szData = ""; // 声明,并初始化字符串数据域,作为共享数据 } class ThreadDemo extends Thread { private ShareData oShare; // 声明,并初始化ShareData 数据域 ThreadDemo() { } // 声明,并实现ThreadDemo 构造方法 // 声明,并实现ThreadDemo 带参数的构造方法 ThreadDemo(String szName, ShareData oShare) { super(szName); // 调用父类的构造方法 this.oShare = oShare; // 初始化oShare域 } public void run() { for (int i = 0; i < 5; i++) { if (this.getName().equals("Thread1")) { oShare.szData = "这是第 1 个线程"; // 为了演示产生的问题,这里设置一次睡眠 try { Thread.sleep((int) Math.random() * 100); // 休眠 } catch (InterruptedException e) { // 捕获异常 } System.out.println(this.getName() + ":" + oShare.szData); // 输出字符串信息 } else if (this.getName().equals("Thread2")) { oShare.szData = "这是第 2 个线程"; // 为了演示产生的问题,这里设置一次睡眠 try { Thread.sleep((int) Math.random() * 100); // 线程休眠 } catch (InterruptedException e) // 捕获异常 { } System.out.println(this.getName() + ":" + oShare.szData); // 输出字符串信息 } } } } public class ThreadNoSynchronized { public static void main(String argv[]) { ShareData oShare = new ShareData(); // 创建,初始化ShareData对象oShare ThreadDemo th1 = new ThreadDemo("Thread1", oShare); // 创建线程th1 ThreadDemo th2 = new ThreadDemo("Thread2", oShare); // 创建线程th2 th1.start(); // 启动线程th1 th2.start(); // 启动线程th2 } }
运行结果如下:
Thread1:这是第 2 个线程
Thread1:这是第 1 个线程
Thread1:这是第 1 个线程
Thread1:这是第 1 个线程
Thread1:这是第 1 个线程
Thread2:这是第 2 个线程
Thread2:这是第 2 个线程
Thread2:这是第 2 个线程
Thread2:这是第 2 个线程
Thread2:这是第 2 个线程
程序中预想的结果是:“Thead1:这是第1 个线程”或“Thead2:这是第2 个线程”,但是线程对数据的异步操作导致运行结果出现了差错。 上面程序是由于线程不同步而导致错误。 为了解决此类问题,Java 提供了“锁”机制实现线程的同步。
锁机制的原理是每个线程进入共享代码之前获得锁,否则不能进入共享代码区,并且在退出共享代码之前释放该锁,这样就解决了多个线程竞争共享代码的情况,达到线程同步的目的。Java中锁机制的实现方法是共享代码之前加入 synchronized 关键字。
在一个类中,用关键字 synchonized 声明的方法为同步方法。Java 有一个专门负责管理线程对象中同步方法访问的工具——同步模型监视器,它的原理是为每个具有同步代码的对象准备惟一的一把“锁”。当多个线程访问对象时,只有取得锁的线程才能进入同步方法,其他访问共享对象的线程停留在对象中等待,如果获得锁的线程调用wait方法放弃锁,那么其他等待获得锁的线程将有机会获得锁。当某一个等待线程取得锁,它将执行同步方法,而其他没有取得锁的线程仍然继续等待获得锁。当把一语句块声明为 synchornized,在同一时间,它的访问线程之一才能执行该语句块。
1) 方法同步:用关键字 synchonized 可将方法声明为同步,格式如下。
class 类名{ public synchonized 类型名称 方法名称(){ ...... } }2)语句块同步: 对于同步块,synchornized 获取的是参数中的对象锁。
synchornized(obj) { //…………………. }当线程执行到这里的同步块时,它必须获取 obj 这个对象的锁才能执行同步块;否则线程只能等待获得锁。必须注意的是obj对象的作用范围不同,控制情况不尽相同。示例如下。
public void method() { Object obj= new Object(); //创建局部Object类型对象obj synchornized(obj) //同步块 { //…………….. } }
上面的代码创建了一个局部对象obj。由于每一个线程执行到 Object obj = new Object()时都会产生一个 obj 对象,每一个线程都可以获得创建的新的 obj对象的锁,不会相互影响,因此这段程序不会起到同步作用。
3)同步类的属性:如果同步的是类的属性,情况就不同了。同步类的成员变量的一般格式如下。
class method { Object o = new Object(); //创建Object类型的成员变量o public void test() { synchornized(o) //同步块 { //……………………… } } }
当两个并发线程访问同一个对象的 synchornized(o)同步代码块时,一段时间内只能有一个线程运行。另外的线程必须等到当前线程执行完同步代码块释放锁之后,获得锁的线程将执行同步代码块。
有时可以通过下面的格式声明同步块。
public void method() { synchornized(this) //同步块 { //……………………… } }
synchornized(ClassName.class) { //……………………. }
5. 线程通信
生产者与消费者是个很好的线程通信的例子,生产者在一个循环中不断生产共享数据,而消费者则不断消费生产者生产的共享数据。程序必须保证有共享数据,如果没有,消费者必须等待生产新的共享数据。两者之间的数据关系如下:
1) 生产者生产前,如果共享数据没有被消费,则生产等待;生产者生产后,通知消费者消费。
2)消费者消费前,如果共享数据已经被消费完,则消费者等待;消费者消费后,通知生产者生产。
为了解决生产者和消费者的矛盾,引入了等待/通知(wait/notify)机制。
class Producer extends Thread { Queue q; Producer(Queue q) { this.q = q; } public void run() { for (int i = 1; i < 5; i++) { q.put(i); } } } class Consumer extends Thread { Queue q; // 声明队列q Consumer(Queue q){ this.q = q; // 队列q初始化 } public void run() { while (true) {// 循环消费元素 q.get(); // 获取队列中的元素 } } }
class Queue { int value = 0; // 声明,并初始化整数类型数据域value boolean isEmpty = true; // 声明,并初始化布尔类型数据域isEmpty,用于判断队列的状态 // 生产者生产方法 public synchronized void put(int v) { // 如果共享数据没有被消费,则生产者等待 if (!isEmpty) { try { System.out.println("生产者等待"); wait(); // 进入等待状态 } catch (Exception e) // 捕获异常 { e.printStackTrace(); // 异常信息输出 } } value += v; // value值加v isEmpty = false; // isEmpty赋值为false System.out.println("生产者共生产数量:" + v); notify(); } public synchronized int get() { if (isEmpty) { try { System.out.println("消费者等待"); wait(); } catch (Exception e) { e.printStackTrace(); } } value--; if (value < 1) { isEmpty = true; } System.out.println("消费者消费一个,剩余:" + value); notify(); return value; } }
生产者调用put方法生产共享数据,如果共享数据不为空,生产者线程进入等待状态;否则将生成新的数据,然后调用notify方法唤醒消费者线程进行消费;
消费者调用get方法消费共享数据,如果共享数据为空,消费者进入等待状态,否则将消费共享数据,然后提调用notify方法唤醒生产者线程进行生产。
public class ThreadCommunication { public static void main(String[] args) { Queue q = new Queue(); Producer p = new Producer(q); Consumer c = new Consumer(q); c.start(); p.start(); } }
6. 线程死锁
一个线程会一直处于等待另一个对象的状态, 而另一个对象又会处于等待下一个对象的状态,以此类推,这个线程“等待”状态链会发生很糟糕的情形,即封闭环状态(也就是说最后那个对象在等待第一个对象的锁)。此时,所有的线程都陷入毫无止境的等待状态中,无法继续运行,这种情况就称为“死锁”。虽然这种情况发生的概率很小,一旦出现,程序的调试变得困难而且查错也是一件很麻烦的事情。
下面举一个死锁的例子。
public class ThreadLocked implements Runnable { public static boolean flag = true; // 起一个标志作用 private static Object A = new Object(); // 声明,并初始化静态Object数据域A private static Object B = new Object(); // 声明,并初始化静态Object数据域B public static void main(String[] args) throws InterruptedException { Runnable r1 = new ThreadLocked(); // 创建,并初始化ThreadLocked对象r1 Thread t1 = new Thread(r1); // 创建线程t1 Runnable r2 = new ThreadLocked(); // 创建,并初始化ThreadLocked对象r2 Thread t2 = new Thread(r2); // 创建线程t2 t1.start(); // 启动线程t1 t2.start(); // 启动线程t2 } public void AccessA() { flag = false; // 初始化域flag // 同步代码快 synchronized (A) { // 声明同步块,给对象A加锁 System.out.println("线程t1 : 我得到了A的锁"); // 输出字符串信息 try { // 让当前线程睡眠,从而让另外一个线程可以先得到对象B的锁 Thread.sleep(1000); // 休眠 } catch (InterruptedException e) { // 捕获异常 e.printStackTrace(); // 异常信息输出 } System.out.println("线程t1 : 我还想要得到B的锁"); // 在得到A锁之后,又想得到B的锁 // 同步块内部嵌套同步块 synchronized (B) { // 声明内部嵌套同步块,指定对象B的锁 System.out.println("线程t1 : 我得到了B的锁"); // 输出字符串信息 } } } public void AccessB() { flag = true; // 修改flag的值 // 同步代码块 synchronized (B) { // 指定同步块,给B加锁 System.out.println("线程t2 : 我得到了B的锁"); // 输出字符串信息 try { // 让当前线程睡眠,从而让另外一个线程可以先得到对象A的锁 Thread.sleep(1000); // 休眠 } catch (InterruptedException e) { // 捕获异常InterruptedException e.printStackTrace(); // 异常信息输出 } System.out.println("线程t2 : 我还想要得到A的锁"); // 字符串信息输出 // 在得到B锁之后,又想得到A的锁 // 同步块内部嵌套内部快 synchronized (A) { // 指定同步块,给A加锁 System.out.println("线程t2 : 我得到了A的锁"); // 输出字符串信息 } } } public void run() { if (flag){ // 当flag为true,执行下面语句 AccessA(); // 调用AccessA方法 } else { AccessB(); // 调用AccessB方法 } } }
Java 语言本身并没有提供防止死锁的具体方法,但是在具体程序设计时必须要谨慎,以防止出现死锁现象。通常在程序设计中应注意,不要使用 stop()、suspend()、resume()以及 destroy()方法。 stop()方法不安全,它会解除由该线程获得的所有对象锁,而且可能使对象处于不连贯状态,如果其他线程此时访问对象,而导致的错误很难检查出来。suspend()/resume ()方法也极不安全,调用 suspend()方法时,线程会停下来,但是该线程并没有放弃对象的锁,导致其他线程并不能获得对象锁。调用destroy()会强制终止线程,但是该线程也不会释放对象锁。