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

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

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

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

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

blocked是指由于某种原因导致running中的线程暂停执行,被放到阻塞队列中,cpu不会再给他们分配时间片,直到导致阻塞的原因被解除而变为runnable状态。

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


另外还有join()和yield()方法,

其中join的使用方式是threadname.join(),他的作用是让当前线程(注意当前线程不是threadname,而是执行这个语句的线程)阻塞,等待threadname线程执行完之后再执行,这也是我们所说的线程合并,因为它相当于把本来两条单独的执行路线合并到一起了。


但是yeild()方法根本都不是阻塞,他是让线程主动放弃cpu,等待下次调度,并没有被放入阻塞队列。

最后,如果一个程序线程很多,某些线程阻塞之后要想让其尽快解除阻塞去运行,就需要尽快让阻塞解除的条件发生。这时,需要弄清楚阻塞条件,如果有其他线程能加快该阻塞线程的阻塞解除速度,那么可以调整其他未阻塞线程的优先级,让未阻塞线程尽快多的执行从而加速阻塞线程解除阻塞,这里也就是动态调整线程优先级的方法来提高整个系统的运行效率。如果是IO阻塞,例如网络阻塞,那么这个时候可以从网络方面入手,如果是文件阻塞可以从磁盘IO入手。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值