这里将自己学习java及其应用的一些笔记、积累分享一下,如果涉及到了文章、文字侵权,请联系我删除或调整。
一、生产者、消费者模型
1.1 概述
- 生产者和消费者是线程间通信的一种模型,这个问题是线程模型中的一个经典问题:
生产者和消费者在同一时间段内共用同一段存储空间,生产者往存储空间中添加产品,消费者从存储空间中取走产品,当存储空间为空时,消费者阻塞,当存储空间满时,生产者阻塞。
这里我们可以举一个简单的例子,上菜!
厨师利用原材料做好一份美味的菜肴————生产者 制造数据
厨师将做好的菜肴放入待取餐区餐柜,敲响取餐铃,通知客人取餐————生产者 将数据放入缓冲区
客人从待取餐区餐柜取走自己的餐食————消费者 把数据从缓冲区取出
客人进食自己的餐食————消费者 加工/处理数据
1.2 相关方法
- 生产者消费者模型中
消费者暂停等待数据
生产者产生数据后,敲铃,发出通知
- Object 的方法
如果线程调用了对象的wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
wait(),将当前线程置于“就绪队列”,并在wait()所在的代码处停止,等待唤醒通知。
wait(long timeout),将当前线程置于“就绪队列”,并在wait()所在的代码处停止,等待唤醒通知(其他线程调用notify(), notifyAll(),或超过指定的时间量)。
wait(long timeout, int nanos),将当前线程置于“就绪队列”,并在wait()所在的代码处停止,等待唤醒通知(其他线程调用notify(), notifyAll(),或其他某个线程中断当前线程,或超过指定的时间量)。
当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。
notify(),只唤醒一个等待(对象的)线程,该线程可竞争对象锁。如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。
notifyAll(),唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。
例如,
调用 stack.wait() ,当前线程,在 stack 对象上等待
调用 stack.notifyAll() ,在 stack 上发出通知,通知 / 唤醒在stack对象上等待的线程
- 上述方法在使用时,必须在 synchronized 同步代码内调用
- 上述方法在使用时,必须在加锁的对象上等待或通知
synchronized(a) {
a.wait()
a.nitifyAll()
}
- 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它会继续留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
- 为了保证安全,wait()外面总应该是一个循环判断
在多线程中,循环判断(或要测试某个条件的变化)使用if 还是while?
要注意,notify唤醒沉睡的线程后,线程会接着上次的执行继续往下执行。所以在进行条件判断时候,可以先把 wait 语句忽略不计来进行考虑;显然,要确保程序一定要执行,并且要保证程序直到满足一定的条件再执行,要使用while进行等待,直到满足条件才继续往下执行。
1.3 练习:生产者、消费者模型的简单实现
package 生产者消费者模型;
/*
* 生产者消费者模型-数据仓库Stack(即我们上面提到的数据缓冲区)
*/
public class Stack {
private char[] c = new char[5];
private int index;
// 入栈操作
public void push(char tmp) {
if (isFull()) {
return;
}
c[index] = tmp;
index++;
}
// 弹栈操作
public char pop() {
if (isEmpty()) {
return ' ';// 用“空格”表示没有数据
}
index--;
char tmp = c[index];
return tmp;
}
// 判断Stack数据是已满
public boolean isFull() {
return index == c.length;
}
// 判断Stack数据是否为空
public boolean isEmpty() {
return index == 0;
}
// 返回栈对象
public char[] getStack() {
return this.c;
}
// 返回当前栈顶的数据下标
public int getIndex() {
return this.index;
}
}
package 生产者消费者模型;
import java.util.Random;
/*
* 生产者消费者模型-生产者类
*/
public class Producer extends Thread{
private Stack stack;
// 构造传入数据库,与消费者共同维护一个数据库
public Producer(Stack stack) {
this.stack = stack;
}
@Override
public void run() {
while(true) {
// ['a','a+26')='a'+[0,26),随机产生英文小写字符
char c =(char)('a'+new Random().nextInt(26));
// synchronized同步代码块,对象锁
synchronized (this.stack) {
// 添加“等待”,解决数据读取冲突
/* 循环判断苏醒条件,避免其他进程误操作唤醒后读取错误或空数据
*/
while(stack.isFull()) {
// Stack没有数据的话,
try {
stack.wait();
} catch (InterruptedException e) {
// 暂不处理异常
}
}
stack.push(c);// 生产者数据入栈
// 通知“消费者”已放入一个数据,有一个数据可取
stack.notifyAll();
System.out.println("入栈<<"+c);
}
}
}
}
package 生产者消费者模型;
/*
* 生产者消费者模型-消费者类
*/
public class Consumer extends Thread{
private Stack stack;
// 构造传入数据库,与生产者共同维护一个数据库
public Consumer(Stack stack) {
this.stack = stack;
}
@Override
public void run() {
while(true) {
// synchronized同步代码块,对象锁
synchronized (this.stack) {
// 添加“等待”,解决数据读取冲突
/* 循环判断苏醒条件,避免其他进程误操作唤醒后读取错误或空数据
*/
while(stack.isEmpty()) {
// Stack没有数据的话,
try {
stack.wait(); // 等待
} catch (InterruptedException e) {
// 暂不处理异常
}
}
char c = stack.pop();
// 通知“生产者”已取出一个数据,有一个位置可放
stack.notifyAll();
System.out.println("弹栈>>"+c);// 栈空时,弹出空格字符
}
}
}
}
package 生产者消费者模型;
public class Test1 {
public static void main(String[] args) {
Stack stack = new Stack();
Producer p = new Producer(stack);// 生产者线程
Consumer c = new Consumer(stack);// 消费者线程
p.start();
c.start();
// main线程,死循环发通知唤醒等待的线程进行相应的条件判断,不动数据
// 通过进程在“notify”后,重复判断是否有数据即可
while(true) {
synchronized (stack) {
stack.notifyAll();
}
}
}
}
1.4 线程监视器模型
- 同步监视器
- 遇到 synchronized 关键字,在加锁的对象上会关联一个同步监视器