手把手教你实现Java进程同步实验—(3)编码实现进程同步

579 阅读6分钟

这是我参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战

Hello,你好呀,我是灰小猿,一个超会写bug的程序猿!

身为一名浪漫的程序员,应该如何深入的实现进程同步嘞?今天就来和小猿一探究竟吧😇**【文中程序员表情包大赏】!**

上一篇和大家分享如何使用信号量机制来解决问题,以及进程同步的原理分析。最后就让我们通过实际的编码设计,来实现这样一个过程吧。

2 程序设计

2.1 需求分析

生活中我们经常遇到的生产者/消费者问题其实就是一个典型的进程同步问题,其中的生产者产生资源,消费者消耗资源。比如典型的买包子问题,我们可以通过它来模拟进程的同步。消费者与生产者进程之间的执行都依赖于另一个进程的消息,想要表现同步机制,这需要使用Java中的wait() / notify()方法实现同步机制。由于包子余量(资源数量)需要所有进程共享,因此任意时刻只能有一个进程访问缓冲器,这需要使用Java中的synchronized同步代码块实现,synchronized关键字的作用就是控制多个线程访问资源同步性的问题,它的三种使用方式是:修饰实例方法、修饰静态方法、修饰代码块。

如果方法或代码块用 synchronized 进行声明,那么对象的锁将保护整个方法或代码块,要调用这个方法或者执行这个代码块,必须获得这个对象的锁。而且,任何时候都只能有一个线程对象执行被保护的代码。

2.2 算法设计思路

我们以买包子问题为例,现设计2个厨师(生产者),2个顾客(消费者),包子铺包子存储上限为3(缓冲器大小)。包子初始值为0,此时所有买家进程会进入等待状态,所有的厨师进程会在包子余量不超过缓冲器大小前不停做包子,并唤醒买家进程已经有包子可吃了,直至缓冲器满了进入等待状态,而买家进程每吃掉一个包子后都会唤醒厨师进程可以继续做包子了。同时由于包子余量需要所有进程共享,保证任意时刻只能有一个进程访问缓冲器,因此所有进程方法都需要用synchronized声明。

3 源代码清单

3.1 缓冲器公共类

package com.common;

/**
 * 定义缓冲器余量,生产者和消费者执行的方法
 */
public class BaoZi {
    Integer count = 0;      //记录包子总数

    /**
     * 生产包子(产生资源)
     *
     * @param name 厨师名
     */
    public synchronized void makeBaoZi(String name) {
//        判断包子数是否大于3达到上限
        while (count >= 3) {
            System.out.println(name + ":包子已经达到上限了!");
            try {
                wait();     //包子容量达到3,超出上限,生产者进入等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        count++;    //如果没有达到3就做一个包子
        System.out.println(name + ":做了一个包子,还剩【" + count + "】个!");
        notifyAll();    //有包子资源了,唤醒消费者购买包子
    }

    /**
     * 购买包子(使用资源)
     *
     * @param name
     */
    public synchronized void buyBaoZi(String name) {
//        判断包子资源数是否等于0,
        while (count == 0) {
            System.out.println(name + ":没有包子资源了!");
            try {
                wait();     //没有包子资源,消费者进程进入等待状态
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        count--;    //包子资源数量减一
        System.out.println(name + ":购买了一个包子,包子资源剩余【" + count + "】个!");
        notifyAll();    //包子数未达到上限,让生产者厨师做包子,(唤醒所有等待该资源的进程),
    }
}

3.2 生产者进程类

package com.producer;

import com.common.BaoZi;

/**
 * 生产者进程
 */
public class BaoZiPu extends Thread {
    private String name;    //厨师名字
    private BaoZi baoZi;    //缓冲区包子资源

    public BaoZiPu() {
    }

    public BaoZiPu(BaoZi baoZi, String name) {
        this.baoZi = baoZi;
        this.name = name;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(2000);     //休眠两秒钟
                baoZi.makeBaoZi(name);  //生产一个包子资源
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

3.3 消费者进程类

package com.consumer;

import com.common.BaoZi;

/**
 * 消费者进程,消耗包子资源
 */
public class Customer extends Thread {

    private String name;
    private BaoZi baoZi;

    public Customer() {
    }

    public Customer(BaoZi baoZi, String name) {
        this.baoZi = baoZi;
        this.name = name;
    }

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(2000); //休眠两秒钟
                baoZi.buyBaoZi(name);   //消耗一个包子资源
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

3.4 测试类

import com.common.BaoZi;
import com.consumer.Customer;
import com.producer.BaoZiPu;

public class TestMain {

    public static void main(String[] args) {
        System.out.println("==========进程同步测试==========");
        BaoZi baoZi = new BaoZi();
        Thread rouBaoZi = new Thread(new BaoZiPu(baoZi, "肉包子厨师"));
        Thread suBaoZi = new Thread(new BaoZiPu(baoZi, "素包子厨师"));
        Thread customerZS = new Thread(new Customer(baoZi, "顾客张三"));
        Thread customerLS = new Thread(new Customer(baoZi, "顾客李四"));
        customerZS.start();
        rouBaoZi.start();
        suBaoZi.start();
        customerLS.start();

    }
}

4 运行结果测试与分析

4.1 测试运行结果

4.2 实验结果分析

根据实验测试结果我们可以做出这样的分析,在我们创建好四个进程时,让四个进程共享一个缓存区资源(包子),之后让顾客张三先去购买包子,结果包子此时是0个,那么该进程进入等待状态,之后素包子厨师和肉包子厨师访问资源开始创建包子,在创建好一个包子资源之后,此时顾客李四也开始去访问包子资源。所有就购买了一个包子。包子数剩余0个,这个时候肉包子厨师的包子做好了,所以现在会有一个包子资源,但是会被等待队列中的张三买走。此时包子资源的数量是没有达到上限的,那么包子厨师就会不断的创建包子资源,同时顾客也会不断的去买包子,直到包子数量为0时,顾客再去购买就会提醒没有包子了。

5 结论

设计这个实验的目的就是为了验证和测试操作系统下进程同步的问题,通过实验学习和代码实践,让我对进程间同步和互斥机制有了更加深刻的认识和理解。在这里我通过包子铺卖包子和买家买包子的案例模拟生产者/消费者问题实现进程间的同步和互斥。

对于生产者和消费者对缓冲区的访问,都是有两个限定条件的。首先对于包子厨师能不能生产包子放到缓冲器中,需要两个条件是:第一,缓冲器需要空闲,即包子余量有没有达到上限;第二,获取当前资源对象的锁,判断有没有其他生产者或消费者在缓冲器中。对于顾客购买包子访问缓冲器也需要两个条件:第一是缓存器中存在资源,也就是有包子,第二是判断有没有其他生产者或消费者在缓冲器中,这都是需要synchronized关键字同步代码块来实现的。

浪漫的程序员朋友。你学会了吗?评论区留言,说出你认为最浪漫的程序员情话!

觉得不错,记得🤞🏻一键三连🤞🏻哟!

**我是灰小猿,我们下期见!**💘💘💘