Java学习 -- 线程(4)

同步的概念

通常,一些同时运行的线程需要共享数据。在这种时候,每一个线程就必须要考虑与其他一起共享数据的线程的状态与行为,否则的话就不能保证共享数据的一致性,从而也就不能保证程序的正确性

class Stack {
	int index = 0;
	char[] data = new char[6];
	public void push(char c) {
		data[index] = c;
		index++;
	}
	public char pop() {
		index--;
		return data[index];
	}
}
  • 当有两个线程A和B同时使用了Stack类的一个对象时,现在我要求:先把r存入Stack中,再将r取出来
    下面的步骤详细演示了AB线程不同步所带来的问题
    (1)操作之前,堆栈中有两个字符:
    data = |a|c| | | | | index = 2
    (2)A执行push中的第一条语句data[index] = ‘r’:
    data = |a|c|r| | | | index = 2 // 2还没有被存入
    (3)A还没有执行index++语句,A被B中断,B执行pop()方法,返回‘c’:
    data = |a|c|r| | | | index = 1 // 取出的是‘c’却不是‘r’
    (4)A继续执行index++语句:
    data = |a|c|r| | | | index = 2
  • 最终结果是:‘r’没有被存入,取出的是‘c’而不是‘r’

生成和消费

在这里插入图片描述
问题描述:假设中间的框是一个栈,我们要不断地向stack中放入数据,也要不断地从stack中取出数据,如何保证生产线程与消费线程达到数据的同步,这就是我们研究的问题。

  • 一个仓库最多容纳6个产品,制造商现在要制造20件产品存入仓库,消费者要从仓库取出这20件产品来消费
  • 制造商制造产品和消费者取出产品的速度很可能是不一样的,编程实现两者的同步
class SynStack {
	private char[] data = new char[6];
	private int cnt = 0; // 表示数组有效元素的个数
	public synchronized void push(char ch) {
		while (cnt == data.length) {
			try {
				this.wait(); // 7行 暂停
			} catch (Exception e) { }
		}
		this.notify(); //10行  叫醒对应线程执行,就是那个因为等待而陷入阻塞的线程
		data[cnt] = ch;
		++cnt;
		System.out.printf("生产线程正在生产第%d个产品,该产品是:%c\n", cnt, ch);
	}
	public synchronized char pop() {
		char ch;
		while (cnt == 0) {
			try {
				this.wait(); // 暂停
			} catch (Exception e) { }
		}
		this.notify(); //21行  叫醒对应线程执行
		ch = data[cnt-1];
		System.out.printf("消费线程正在消费第%d个产品,该产品是:%c\n", cnt, ch);
		--cnt;
		return ch; // 25行
	}
}
class Producer implements Runnable { // 生产线程
	private SynStack ss = null;
	public Producer(SynStack ss) {
		this.ss = ss;
	}
	public void run() {
		char ch;
		
		for (int i=0; i<20; ++i) {
			try {
				Thread.sleep(2000);
			} catch(Exception e) {
			}
			ch = (char)('a' + i);
			ss.push(ch);
		}
			
	}
}
class Consumer implements Runnable { // 消费线程
	private SynStack ss = null;
	public Consumer(SynStack ss) {
		this.ss = ss;
	}
	public void run() {
		for (int i=0; i<20; ++i) {
			// System.out.printf("%c\n", ss.pop());
			ss.pop();
		}
	}
}
public class TestPC {
	public static void main(String[] args) {
		SynStack ss = new SynStack();
		Producer p = new Producer(ss);
		Consumer c = new Consumer(ss);

		// 生产
		Thread t1 = new Thread(p);
		t1.start();

		// 消费
		Thread t2 = new Thread(c);
		t2.start();
	}
}

在这里插入图片描述

notify 和 wait 方法

  • this.notify();
  • 功能:
    (1)不是叫醒正在执行this.notify()的当前线程
    (2)而是叫醒一个现在正在wait this对象的其他线程,如果有多个线程正在wait this对象
    (3)通常是叫醒最先wait this 对象的线程,但是具体是叫醒哪一个
    (4)这是由系统调度器控制,程序员无法控制
假设现在有T1、T2、T3、T4四个线程
我们在T4线程中执行了aa.notify()语句
则即便此时T1 T2 T3没有一个线程因为wait aa对象而陷入阻塞状态,T4线程中执行aa.notify方法时也不会有任何错误
本程序就证明了这一点
执行aa.notify方法时如果一个线程都没有叫醒,这是可以的

notify 和 wait 方法总结

  • aa.wait()
    1. 将执行aa.wait()的当前线程转入阻塞状态,让CPU的控制权释放对aa的锁定
  • aa.notify()
    1. 假设执行aa.notify()的当前线程为T1
    2. 如果当前时刻有其他线程因为执行了aa.wait()而陷入阻塞状态,则叫醒其中的一个
    3. 所谓叫醒某个线程就是令该线程从因为wait而陷入阻塞的状态转入就绪状态
  • aa.notifyAll()
    1. 叫醒其他所有的因为执行了aa.wait()而陷入阻塞状态的线程

要注意的问题

  • 执行完21行的代码后,程序绝对不会立即切换到另一个线程
  • 21行代码叫醒的是其他线程,叫醒的不是本线程
  • 在最开始,P和C刚开始执行时,即便P没有wait,也可以在C中notify,即便C没有wait,也可以在P中notify
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值