java中线程阻塞之sleep、suspend、wait、resume、notify方法解析

java线程的5状态包括create、runnable、running、blocked、dead。

create是指用new线程被创建,但是还没有准备好各种资源。

runnable是指使用start启动线程,这时候系统会为线程分配除cpu以外的所有需要的资源。

running是指cpu调度处于runnable状态的线程使其占用cpu时间片真正开始运行。

blocked是指由于某种原因导致running中的线程暂停执行,被放到阻塞队列中,知道导致阻塞的原因被解除。

dead指线程结束,包括正常结束(如执行完run方法)和非正常结束(如抛出异常等)。

那么造成blocked的原因有哪些呢?

(1) 调用 sleep(毫秒数),使线程进入“睡眠”状态。在规定的时间内,这个线程是不会运行的。(2) 用 suspend()暂停了线程的执行。除非线程收到 resume() 消息,否则不会返回“可运行”状态。(3) 用 wait()暂停了线程的执行。除非线程收到 nofify() 或者 notifyAll()消息,否则不会变成“可运行”(是的,看起来同原因 2 非常相象,但有一个明显区别是我们马上要揭示的)(4) 线程正在等候一些 IO(输入输出)操作完成。(5) 线程试图调用另一个对象的“同步”方法,但那个对象处于锁定状态,暂时无法使用。

先写两个基类:

class Blockable extends Thread {private Peeker peeker;protected int i;public Blockable(){peeker = new Peeker(this);}public synchronized int read(){ return i;}public synchronized void update(){System.out.println(this.getClass().getName()+"state :i = " + i);}public void stopPeeker(){peeker.terminate();}}class Peeker extends Thread {private Blockable b;private int session;private boolean stop = false;public Peeker(Blockable b){this.b = b;start();}public void run(){while (!stop) {System.out.println(b.getClass().getName()+ " Peeker " + (++session)+ "; value = " + b.read());try {sleep(1000);} catch (InterruptedException e){}}}public void terminate() {stop = true;}}Blockable 类打算成为本例所有类的一个基础类。一个 Blockable 对象包含了一个i信息。用于显示这些信息的方法叫作 update() 。我们发现它用getClass.getName() 来产生类名,而不是仅仅把它打印出来;这是由于 update()不知道自己为其调用的那个类的准确名字,因为那个类是从 Blockable 衍生出来的。在 Blockable 中,变动指示符是一个 int i;衍生类的 run()方法会为其增值。针对每个 Bloackable 对象,都会启动 Peeker 类的一个线程。Peeker 的任务是调用 read()方法,,检查与自己关联的 Blockable 对象,看看 i 是否发生了变化,最后打印检查结果。注意 read()和 update() 都是同步的,要求对象的锁定能自由解除,这一点非常重要。

(1)sleep方法的调用引起其他线程阻塞是因为sleep不会释放对象锁。下是测试代码:

class Sleeper1 extends Blockable {public Sleeper1() {super();}public synchronized void run() {while(true) {i++;update();try {sleep(1000);} catch (InterruptedException e){}}}}class Sleeper2 extends Blockable {public Sleeper2() {super();}public void run() {while(true) {change();try {sleep(1000);} catch (InterruptedException e){}}}private synchronized void change(){i++;update();}}public class SleepBlock {public static void main(String[] args) {new Sleeper1().start();new Sleeper2().start();}}我们会发现Sleeper1中的run方法是同步的,由于run方法里面是死循环,当Sleeper1线程启动后,Sleeper1内的Peeker线程根本无法运行。因为Sleeper1的sleep方法并不会释放对象锁。

在 Sleeper1 中,整个 run()方法都是同步的。我们可看到与这个对象关联在一起的 Peeker 可以正常运行,直到我们启动线程为止,随后 Peeker 便会完全停止。这正是“堵塞”的一种形式:因为 Sleeper1.run()是同步的,而且一旦线程启动,它就肯定在 run()内部,方法永远不会放弃对象锁定,造成 Peeker 线程的堵塞。 Sleeper2 通过设置不同步的运行,提供了一种解决方案。只有 change() 方法才是同步的,所以尽管 run()位于 sleep()内部,Peeker 仍然能访问自己需要的同步方法——read()。在这里,我们可看到在启动了Sleeper2 线程以后,Peeker 会持续运行下去。

(2)suspend方法调用引起其他线程阻塞也是因为suspend方法不会释放对象锁,这是不安全的。此方法已经废弃。因为使用suspend会发生一种很愚蠢的现象,就是自己把自己挂起,也就是进入阻塞状态。但是还抢着对象锁(前提是有同步方法访问同样的资源),这样其他线程来访问同步方法也只能傻傻等待。就好像你自己拿着钥匙在那等别人来开门,别人来开你又不给钥匙。

(3)wait方法阻塞自己会释放对象锁,这一点和sleep、suspend是根本区别,因此他不会阻碍其他线程。

若必须等候其他某些条件(从线程外部加以控制)发生变化,同时又不想在线程内一直傻乎乎地等下去,一般就需要用到 wait()。wait()允许我们将线程置入“睡眠”状态,同时又“积极”地等待条件发生改变。而且只有在一个 notify() 或 notifyAll()发生变化的时候,线程才会被唤醒,并检查条件是否有变。因此,我们认为它提供了在线程间进行同步的一种手段。

我们也可以看到 wait()的两种形式。第一种形式采用一个以毫秒为单位的参数,它具有与 sleep()中相同的含义:暂停这一段规定时间。区别在于在 wait()中,对象锁已被解除,而且能够自由地退出 wait(),因为一个 notify() 可强行使时间流逝。第二种形式不采用任何参数,这意味着 wait()会持续执行,直到 notify() 介入为止。而且在一段时间以后,不会自行中止。

(4)IO引起的阻塞。这一部分主要是IO那一块的。由于访问IO可能会等待,因此会引起阻塞。

(5)在上面已经可以看出来了,不同线程在访问相同对象的同步方法时,会引起阻塞。需要等待一方先完成才能进行。

爱的力量大到可以使人忘记一切,

java中线程阻塞之sleep、suspend、wait、resume、notify方法解析

相关文章:

你感兴趣的文章:

标签云: