同步的概念
通常,一些同时运行的线程需要共享数据。在这种时候,每一个线程就必须要考虑与其他一起共享数据的线程的状态与行为,否则的话就不能保证共享数据的一致性,从而也就不能保证程序的正确性
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()
- 将执行aa.wait()的当前线程转入阻塞状态,让CPU的控制权释放对aa的锁定
- aa.notify()
- 假设执行aa.notify()的当前线程为T1
- 如果当前时刻有其他线程因为执行了aa.wait()而陷入阻塞状态,则叫醒其中的一个
- 所谓叫醒某个线程就是令该线程从因为wait而陷入阻塞的状态转入就绪状态
- aa.notifyAll()
- 叫醒其他所有的因为执行了aa.wait()而陷入阻塞状态的线程
要注意的问题
- 执行完21行的代码后,程序绝对不会立即切换到另一个线程
- 21行代码叫醒的是其他线程,叫醒的不是本线程
- 在最开始,P和C刚开始执行时,即便P没有wait,也可以在C中notify,即便C没有wait,也可以在P中notify