第三章线程间的通信第一节

3.1.1不使用等待/通知机制实现线程间通信

3.1.2什么是等待/通知机制

两个线程完全是主动式地读取一个变量,在花费读取时间的基础上,读到的数据并不确定是否是想要的,因此需要“等待通知”机制

3.1.3等待/通知机制的实现

1wait()方法:作用是使当前执行代码的线程进行等待,该方法是Object类的方法,用来将当前线程置入“预执行队列”中,并在wait()所在代码行处停止执行,直到接到通知或被中断为止。在调用wait()方法之前,线程必须获得该对象的对象级别锁,即只能在同步方法或同步代码块中调用wait()方法。在执行wait()方法后,当前线程释放锁。在从wait()返回前,线程与其他线程竞争重新获得锁。如果在调用wait()时没有持有适当的锁,则会抛出异常,而不是try/catch捕获异常,因为该异常是RuntimeException的一个子类

2notify()方法:也要在同步方法或同步代码块中调用,如果调用notify()时没有持有适当的锁,也会抛出和wait()一样的异常。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出其中一个呈wait状态的线程,对其发出通知notify,并使它等待获取该对象的对象锁。

在执行notify方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁

总结:两个方法都要获得该对象的对象级别锁才能调用,wait()执行后,当前线程立即释放该对象的对象锁;notify()执行后,当前线程要等到同步代码块执行完毕后才能释放锁,并唤醒其他线程

这两个方法是Object类型的方法,在进行等待和通知时都是由锁对象自己来调用方法的

wait()方法可以使调用该方法的线程释放共享资源的锁,然后从运行状态退出,进入等待队列,直到被再次唤醒

notify()方法可以随机唤醒等待队列中等待同一共享资源的一个线程,使线程从等待状态进入运行状态

notifyAll()方法可以是所有正在等待队列中等待同一共享资源的“全部线程”从等待状态退出,进入运行状态

线程的四种状态

1)新创建一个新的线程对象后,再调用它的start()方法,系统会为此线程分配CPU资源,使其处于Runnable(可运行)状态,这是 一个准备运行的状态。如果线程抢占到了CPU资源,此线程就处于Running(运行)状态

2)Runnable,Running可以互相切换,因为有可能线程一段时间后,有其他高优先级的线程抢占到了CPU资源

线程进入Runnable状态的五种情况:

调用sleep()方法后经过的时间超过了指定的休眠时间

线程调用的阻塞IO已经返回,阻塞方法执行完毕

线程成功地获得了试图同步的监视器

线程正在等待某个通知,其他线程发出了通知

处于挂起状态的线程调用了resume恢复方法

3)Blocked阻塞状态:如果遇到了一个IO操作,而CPU此时处于空闲状态,可能会转而把CPU分给其他线程,这是称为暂停状态

出现阻塞状态的5种情况:

线程调用sleep方法,主动放弃占用处理资源

线程调用了阻塞式IO方法,在该方法返回前,该线程被阻塞

线程试图获取一个同步监视器,但该监视器正被其他线程所持有

线程等待某个通知

程序调用了suspend方法将该线程挂起,此方法容易出现死锁,避免使用

4)run方法运行结束后进入销毁阶段,整个线程执行完毕

每个锁对象都有两个两个队列:一个是就绪队列,一个是阻塞队列

3.1.4方法wait锁释放与notify锁不释放

当方法wait被执行后,锁被自动释放,但执行完notify方法,锁不自动释放,要等到线程执行完同步代码块后释放锁

package three.fourth.test;

import three.fourth.extthread.ThreadA;

public class Test2 {

	public static void main(String[] args) {
//		本例用来说明呈wait状态的线程调用interrupt方法时会出现异常
		Object lock = new Object();
		ThreadA a = new ThreadA(lock);
		a.start();
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		a.interrupt();
	}

}
  1. 执行完同步代码块就会释放对象的锁
  2. 在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放
  3. 在执行同步代码块的过程中,执行力锁所属对象的wait方法,这个线程会释放对象锁,而次线程对象会进入线程等待池中,等待被唤醒

3.1.6只通知一个线程

调用方法notify一次只通知一个线程进行唤醒

当多次调用notify方法时,会随机将等待wait状态的线程进行唤醒

3.1.7唤醒所有线程

当notify方法的调用次数小于线程对象的数量,会出现有部分线程对象对象无法被唤醒的情况,可以使用notifyAll方法唤醒全部线程

3.1.8方法wait(long)的使用

该方法的功能是等待某一时间内是否有线程对锁进行唤醒,如果超过这个时间则自动唤醒

3.1.9通知过早

如果通知过早,会打断程序的正常逻辑

为了防止通知过早的情况,可以设置一个布尔类型的标志位

3.1.10等待wait的条件发生变化

此处体现出if和while的区别,if只能判断一次,而while可以循环判断,可以避免覆盖

3.1.11生产者/消费者模式实现

一生产与一消费:操作值

创建生产者类:

package productandclient;

public class P {
	private String lock;
	public P(String lock) {
		super();
		this.lock = lock;
	}
	public void setValue() {
		synchronized (lock) {
			if(!valueObject.value.equals("")) {
				try {
					lock.wait();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			String value = System.currentTimeMillis()+"_"+System.nanoTime();
			System.out.println("set的值是"+value);
			valueObject.value = value;
			lock.notify();
		}
	}
}

创建消费者类:

package productandclient;

public class C {
	private String lock;
	public C(String lock) {
		super();
		this.lock = lock;
	}
	public void getValue() {
		synchronized (lock) {
		if(valueObject.value.equals("")) {
			try {
				lock.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println("get的值为"+valueObject.value);
		valueObject.value = "";
		lock.notify();
		}
	}
}

创建生产者线程和消费者线程:

package productandclient;

public class ThreadP extends Thread {
	private P p;
	public ThreadP(P p) {
		super();
		this.p = p;
	}
	public void run() {
		while(true) {
			p.setValue();
		}
	}
}
package productandclient;

public class ThreadC extends Thread {
	private C c;
	public ThreadC(C c) {
		super();
		this.c = c;
	}
	public void run() {
		while(true) {
			c.getValue();
		}
	}
}

运行测试类:

package productandclient;

public class Run {

	public static void main(String[] args) {
		String lock = new String("");
		P p = new P(lock);
		C c = new C(lock);
		ThreadP threadp = new ThreadP(p);
		ThreadC threadc = new ThreadC(c);
		threadp.start();
		threadc.start();
	}

}

运行结果如图所示:

可以看出set和get方法是交替执行的,但是谁先执行是随机的,看谁先抢到线程的执行权

多生产与多消费:操作值-假死

“假死”现象就是线程进入了waiting等待状态,如果全部线程都进入了waiting状态,则程序就不再执行任何业务功能了,整个项目呈停止状态

由于在这种模式下生产者可能会唤醒生产者,消费者可能会唤醒消费者,这样积累下去,最后导致所有线程都呈waiting状态,程序最终呈现假死状态

解决方法:将notify方法换成notifyAll方法,不光唤醒同类线程,也唤醒异类线程

解决wait条件改变:if换成while

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值