JAVA学习之路02——并发协作
从“生产者消费者”讲起
谈到并发协作,生产者消费者的这个例子非常经典,通过两类模式可以实现这个关系。为了方便后文的撰写,这里暂且称第一类模式为“协作发展”,第二类模式为“你劳我逸”。
这两类模式的共同点是,当仓库(缓存区)放满后,生产者停止生产,当仓库取空后,消费者停止拿取;不同之处在于,协作发展模式中当生产者开始生产后,消费者同时开始拿取,这取决于CUP的调度,而你劳我逸模式中,生产者必须放满仓库,才会通知消费者拿取,而消费者同样需要取完之后,才会告知生产者开始生产。
具体到代码的书写(代码在文末),协同发展模式就像下图中,生产者和消费者在同一间仓库里疯狂的循环劳动(在自身类中循环调用缓存区中相应的方法)。
你劳我逸模式如下图:生产者在开始工作后(线程被调用后),锁上仓库的门(拿到共享资源)就开始埋头干(在自己的类中循环执行方法),直到把仓库全部填满,就停下来把门打开(释放共享资源)让消费者进来拿资源,直到资源被拿空了,生产者才能重新获得仓库的使用权,开始继续生产。
两个必须要面对的问题
1、锁谁?
锁共享资源。
共享资源是多个线程在同一时刻争夺的对象,这个问题中很明显共享资源是仓库(缓存区)。当一个对象被一个线程修改后,同步共享资源可以阻止另一个线程观察到对象内部修改前的状态,同时可以保证拿到共享资源的线程可以观察到之前线程修改过后的状态。
Integer[] s=new Integer[10];//使用一个数组来模拟仓库
2、怎么锁?
使用synchronized对共享资源上锁。下面通过伪码来解释一下怎么锁的问题:
- 协作发展模式:由于两个方法都是通过调用缓存区内的方法来实现目标的,所以需要在缓存区中上锁,锁住“this”即可。
class 生产者 implements Runnable{
public void run() {
for(循环) {
调用生产产品的方法
}
}
}
class 消费者 implements Runnable{
public void run() {
for(循环) {
调用拿走产品的方法
}
}
}
class 缓存区方法{
Integer[] s=new Integer[10];//仓库
public synchronized void 生产() {
}
public synchronized void 消费(int i) {
}
}
- 你劳我逸模式:这个模式下,两个类分别调用各自的方法,再在各自的方法中调用缓存区的对象为缓存区增删内容,这就产生了一个问题,如何确保生产者消费者进入同一件仓库?换句话说如何确保这两个类操纵的是同一个缓存区对象?
private static Buffer buffer=null;
public static Buffer getBuffer() {
if(buffer==null)
buffer=new Buffer();
return buffer;
}
其实这很容易实现,如上面的代码,只要在缓存区里新建一个对象在封装起来,供外界调用就可以。你劳我逸的伪码就不再赘述了。
最后搭配上wait();和notifyAll();在synchronized中,就可以完成整个模式。这里需要注意的是,wait();、notifyAll();是被synchronized锁住的对象调用的,并且必须在synchronized(){}的大括号内使用。
附上这两个模式的代码和结果图
- 协作发展模式
public class test {
public static void main(String[] args) {
Buffer b=Buffer.getBuffer();
new Thread(new Producer(b)).start();
new Thread(new Consumer(b)).start();
}
}
class Producer implements Runnable{
Buffer b;
public Producer(Buffer b) {
super();
this.b = b;
}
public void run() {
for(int i=1;i<1000;i++) {
b.push(i);
}
System.out.println("*****************生产完毕**********************");
}
}
class Consumer implements Runnable{
Buffer b;
public Consumer(Buffer b) {
super();
this.b = b;
}
public void run() {
for(int i=1;i<1000;i++) {
b.pop(i);
}
System.out.println("*****************消费完毕**********************");
}
}
class Buffer{
private static Buffer buffer=null;
Integer[] s=new Integer[10];
int count=0;//计数器
//生产
public synchronized void push(int i) {
if(s.length-1==count) {
try {
System.out.println("等待消费");
wait();
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
}
}
s[count]=i;
count++;
System.out.println("第"+i+"次生产产品,目前有:"+count+"产品剩余");
notifyAll();
}
//消费
public synchronized void pop(int i) {
if(count==0) {
try {
System.out.println("等待生产");
wait();
} catch (InterruptedException e) {
}
}
count--;
System.out.println("第"+i+"次消费产品,目前有:"+count+"产品剩余");
notifyAll();
}
public synchronized static Buffer getBuffer() {
if(buffer==null)
buffer=new Buffer();
return buffer;
}
}
- 你劳我逸模式
public class test02 {
public static void main(String[] args) {
Producer p=new Producer();
Consumer c=new Consumer();
new Thread(p,"生产者").start();
new Thread(c,"消费者").start();
}
}
class Producer implements Runnable{
Buffer b=Buffer.getBuffer();
public void run() {
synchronized (b) {
for(int i=1;i<20;i++) {
if(b.s.length-1==b.count) {
try {
System.out.println("等待消费");
b.wait();
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
}
}
b.s[b.count]=i;
b.count++;
System.out.println("第"+i+"次生产产品,目前有:"+b.count+"产品剩余");
b.notifyAll();
}
System.out.println("*****************生产完毕**********************");
}
}
}
class Consumer implements Runnable{
public void run() {
Buffer b=Buffer.getBuffer();
synchronized (b) {
for(int i=1;i<20;i++) {
if(b.count==0) {
try {
System.out.println("等待生产");
b.wait();
} catch (InterruptedException e) {
}
}
b.count--;
System.out.println("第"+i+"次消费产品,目前有:"+b.count+"产品剩余");
b.notifyAll();
}
System.out.println("*****************消费完毕**********************");
}
}
}
class Buffer{
Integer[] s=new Integer[5];//缓存
int count=0;//计数器
private static Buffer buffer=null;
public static Buffer getBuffer() {
if(buffer==null)
buffer=new Buffer();
return buffer;
}
}
写在最后
目前JAVA平台已经提供了更高级的并发工具,wait();和notify();已经没有必要再使用了…所以说学技术必须跟上时代!
赶紧去学习java.util.concurrent。