【操作系统作业】睡觉助教(用Java的ReentrantLock实现)

简介: 【操作系统作业】睡觉助教(用Java的ReentrantLock实现)

一、题目

睡觉助教

某大学计算机科学系有一名助教(TA),他在正常的工作时间帮助本科生完成编程任务。助教办公室相当小,只有一张桌子、一把椅子和一台电脑。办公室外面的走廊里有三把椅子,如果他正在帮助一个学生,其它学生可以坐在那里等待。当没有学生在工作时间需要帮助时,助教就坐在桌子旁边打个盹儿。如果学生在办公时间到达,发现助教睡着了,学生必须叫醒助教寻求帮助。如果一个学生来了并且发现他现在正在帮助另一个学生,那么他会坐在走廊里的一把椅子上并等待。如果没有椅子可用,学生稍后会回来。

使用 POSIX 线程、互斥锁和信号量,实现一个协调 TA 和学生活动的解决方 案。此任务的详细信息如下。


学生与 TA


使用 pthreads,首先创建 n 个学生。每个作为单独线程来运行。TA 也将作

为单独的线程运行。学生线程在编程和寻求助教帮助之间交替。如果 TA 有空,

他们将获得帮助。否则,学生要么坐在走廊的椅子上,如果没有椅子可用,将恢 复编程并将在以后寻求帮助。如果学生到达并注意到 TA

正在睡觉,学生应采用 信号量通知他。当 TA 完成帮助一个学生时,必须查看走廊里是否有等待帮助的 学生。如果有,TA

应该依次帮助这些学生。如果没有学生在场,TA 可以继续打 盹。 学生编程和助教为学生提供帮助的最好模拟方法,是让线程随机睡眠一段时 间。

请参考 POSIX 同步中的互斥锁和信号量等相关机制。


二、思路一

1.题目解析

此题的原意是用c语言解决,去调用函数库中的方法,但我之前说了,多进程/多线程是在操作系统层面上的概念,与语言无关(可以用语言去实现,事实上上linux就是用c语言+汇编实现了多进程/线程管理的)


这里我用Java来解决这个题目(当然主要是因为我比较熟悉Java)。


回到正题,我们先把此题翻译一下:

有多个学生(多个线程),每隔一段时间学生线程就会去老师办公室看看(状态信号量),如果助教在打盹,那么叫醒助教(锁住并占有资源),如果老师在辅导学生,那么该学生线程就会去看看外面三个位子(空位信号量)有没有空的,如果有空的,则占有一个位子,否则回去继续编程。


据此,我的思路就是设置两个信号量(status,waitStu),三把锁(lock1,lock2,lock3)。


//可重入锁,synchronized锁太重了
//对status变量加锁
private final Lock lock1=new ReentrantLock();
//对waitStu变量加锁
private final Lock lock2=new ReentrantLock();
//对于请教过程加锁
private final Lock lock3=new ReentrantLock();
//三个座位
private Integer waitStu=3;
//助教的状态,true表示醒着,false表示正在打盹
private boolean status=false;


为什么要三把锁,而不是一把呢?

这是因为我在这里设计的老师类包括了老师本身和外面等待的位置,所以我需要不同的锁保证不同资源的合理访问(当然你也可以不这么设计),同时需要有细粒度的锁来保证status,waitStu这两个信号量的正确性。

所以这三把锁的作用:


lock1:对status变量加锁,保证对status信号量的访问修改正确

lock2:对waitStu变量加锁,保证对waitStu信号量的访问修改正确

lock3:对老师这个资源上锁,如果有多个线程试图访问,未持有该锁的线程将会阻塞

其实这三把锁并无区别,从代码实现上来看都是一个ReentrantLock对象,无其他不同的参数,我们只是在逻辑上赋予它们不同的作用,至于是什么作用取决于它上锁和解锁的时机。


两个信号量的作用:


status:助教的状态,true表示醒着,false表示正在打盹

waitStu:剩余等待位子数量

2.具体代码

代码实现如下:


package com.dreamchaser.concurrent;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * 睡觉助教
 */
public class Proj3_1 {
    public static void main(String[] args) {
        Teacher teacher=new Teacher();
        Student student1=new Student("张三",teacher,1000);
        Student student2=new Student("李四",teacher,2000);
        Student student3=new Student("King",teacher,500);
        Student student4=new Student("jhl",teacher,500);
        Student student5=new Student("王五",teacher,600);
        student1.start();
        student2.start();
        student3.start();
        student4.start();
        student5.start();
        //保持主线程一直运行,避免主线程关闭导致其他线程关闭
        while (true){}
    }
    static class Teacher{
        //可重入锁,synchronized锁太重了
        //对status变量加锁
        private final Lock lock1=new ReentrantLock();
        //对waitStu变量加锁
        private final Lock lock2=new ReentrantLock();
        //对于请教过程加锁
        private final Lock lock3=new ReentrantLock();
        //三个座位
        private Integer waitStu=3;
        //助教的状态,true表示醒着,false表示正在打盹
        private boolean status=false;
        /**
         * 求教助教
         */
        public void consult(String sname){
            lock1.lock();
            //如果老师正在打盹
            if (!status){
                status=true;
                lock1.unlock();
                //这个锁主要用来锁住请教的过程
                lock3.lock();
            }else {
                lock1.unlock();
                //位子满了不用执行下面的了
                if (!wait(sname)){
                    return;
                }
            }
            System.out.println(sname+"正在请教老师,老师正在帮助学生!");
            //让线程停一下,模拟请教老师的过程
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(sname+"请教完了");
            System.out.println("助教打盹了!");
            status=false;
            //解锁
            lock3.unlock();
        }
        /**
         * 等待方法
         * @param sname
         * @return
         */
        private boolean wait(String sname){
            //锁住waitStu
            lock2.lock();
            try {
                if (waitStu>0){
                    waitStu--;
                    //释放该变量的锁(防止修改waitStu时出现错误)
                    //当老师正在帮助学生时,等待
                    System.out.println(sname+"正在坐在座位上等待!剩余座位:"+waitStu);
                    lock2.unlock();
                    //尝试锁住lock3
                    lock3.lock();
                    //当没人请教老师时,空闲座位+1
                    //这里加锁是为了方式修改waitStu信号量时发生错误
                    lock2.lock();
                    waitStu++;
                    System.out.println(sname+"离开座位,去请教助教!剩余座位:"+waitStu);
                    return true;
                }else {
                    System.out.println("没位置了,"+sname+"回去编程了!");
                    return false;
                }
            }finally {
                lock2.unlock();
            }
        }
    }
    static class Student extends Thread{
        private String name;
        private Teacher teacher;
        private long time;
        public Student(String name,Teacher teacher,long time) {
            this.name=name;
            this.teacher=teacher;
            this.time=time;
        }
        @Override
        public void run() {
            while (true){
                teacher.consult(name);
                //模拟间隔时间,隔一段时间问老师,隔一段时间编程
                try {
                    Thread.sleep(time);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(name+"正在编程...");
                try {
                    Thread.sleep(time);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


注:以上代码其实并不优美,对于lock的上锁的和解锁操作并不规范,更规范的做法是用try-catch包裹上锁过程以及执行逻辑,并在finally代码块里释放锁,但因为作者水平原因同时考虑到实现的逻辑,我目前并没有很好的思路去优化,或许随着我不断学习深入,未来会有更好的方法和思路。所以呢,以上代码仅做参考,如有建议欢迎在评论区评论。


3.运行效果截图



q2.png

q1.png

三、思路二(思路优化)

原本博文只有思路一的,但那时我便发现之前的代码并不规范,逻辑也不太清晰,而随着学习深入我对之前的思路进行优化,使代码更加简单规范,遂有了思路二。


1.优化思路

原先的思路一,也就是我开始的想法是定义了两个信号量,三把锁。但后来我发现所谓锁其实就可以看做Boolean类型的信号量,所以实现上就可以只用一个信号量——剩余等待数,而锁也只需两把,一把锁教师,一把锁等待空位。这样代码就简便了很多。

而对于逻辑上来说,当发现老师在工作时应该是查看空位是否还有剩余,而非阻塞,所以用Java里面lock.tryLock()方法会更合适。同时加锁释放锁都在try-catch结构体中进行会更加规范,有利于提升代码可读性和降低代码出错率。


2.代码实现

package com.dreamchaser.concurrent;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * 睡觉助教
 */
public class Proj3_1 {
    public static void main(String[] args) {
        Teacher teacher=new Teacher();
        Student student1=new Student("张三",teacher,1000);
        Student student2=new Student("李四",teacher,2000);
        Student student3=new Student("King",teacher,500);
        Student student4=new Student("jhl",teacher,500);
        Student student5=new Student("王五",teacher,600);
        student1.start();
        student2.start();
        student3.start();
        student4.start();
        student5.start();
        //保持主线程一直运行,避免主线程关闭导致其他线程关闭
        while (true){}
    }
    static class Teacher{
        //可重入锁,synchronized锁太重了
        //对请教老师变量加锁
        private final Lock lock1=new ReentrantLock();
        //对waitStu变量加锁
        private final Lock lock2=new ReentrantLock();
        //三个座位
        private Integer waitStu=3;
        /**
         * 求教助教
         */
        public void consult(String sname){
            if (lock1.tryLock()){
                try {
                    work(sname);
                }finally {
                    lock1.unlock();
                }
            }else {
                //如果没有则等待
                //位子满了不用执行下面的了
                if (!wait(sname)){
                    return;
                }
                //做完等待的事后去尝试获取锁,如果获取不到则被阻塞
                lock1.lock();
                try {
                    //当没人请教老师时,空闲座位+1
                    //这里加锁是为了方式修改waitStu信号量时发生错误
                    lock2.lock();
                    try{
                        waitStu++;
                        System.out.println(sname+"离开座位,去请教助教!剩余座位:"+waitStu);
                    }finally {
                        lock2.unlock();
                    }
                    work(sname);
                }finally {
                    lock1.unlock();
                }
            }
        }
        /**
         * 工作
         * @param sname 学生姓名
         */
        private void work(String sname){
            System.out.println(sname+"正在请教老师,老师正在帮助学生!");
            //让线程停一下,模拟请教老师的过程
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(sname+"请教完了");
            System.out.println("助教打盹了!");
        }
        /**
         * 等待方法
         * @param sname
         * @return
         */
        private boolean wait(String sname){
            //锁住waitStu
            lock2.lock();
            try {
                if (waitStu>0){
                    waitStu--;
                    //释放该变量的锁(防止修改waitStu时出现错误)
                    //当老师正在帮助学生时,等待
                    System.out.println(sname+"正在坐在座位上等待!剩余座位:"+waitStu);
                    return true;
                }else {
                    System.out.println("没位置了,"+sname+"回去编程了!");
                    return false;
                }
            }finally {
                lock2.unlock();
            }
        }
    }
    static class Student extends Thread{
        private String name;
        private Teacher teacher;
        private long time;
        public Student(String name,Teacher teacher,long time) {
            this.name=name;
            this.teacher=teacher;
            this.time=time;
        }
        @Override
        public void run() {
            while (true){
                teacher.consult(name);
                //模拟间隔时间,隔一段时间问老师,隔一段时间编程
                try {
                    Thread.sleep(time);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(name+"正在编程...");
                try {
                    Thread.sleep(time);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


3.运行结果截图


w1.png

结语

以上是我的实现思路。希望我的思路能个各位有所帮助,当然,如果有什么意见或者建议的,欢迎在评论区评论。


相关文章
|
3月前
|
安全 Java 开发者
Java并发编程:深入理解synchronized和ReentrantLock
在Java并发编程中,正确使用同步机制是确保线程安全的关键。本文将深入探讨Java内置的两种同步机制——synchronized关键字和ReentrantLock类。我们将通过权威数据、经典理论和实际案例,对比分析它们的性能、用法和适用场景,帮助开发者做出明智的选择。
27 0
|
17天前
|
小程序 Java 开发工具
【Java】@Transactional事务套着ReentrantLock锁,锁竟然失效超卖了
本文通过一个生动的例子,探讨了Java中加锁仍可能出现超卖问题的原因及解决方案。作者“JavaDog程序狗”通过模拟空调租赁场景,详细解析了超卖现象及其背后的多线程并发问题。文章介绍了四种解决超卖的方法:乐观锁、悲观锁、分布式锁以及代码级锁,并重点讨论了ReentrantLock的使用。此外,还分析了事务套锁失效的原因及解决办法,强调了事务边界的重要性。
44 2
【Java】@Transactional事务套着ReentrantLock锁,锁竟然失效超卖了
|
15天前
|
消息中间件 算法 Java
深入浅出操作系统:进程管理的艺术掌握Java中的异常处理机制
【8月更文挑战第30天】在数字世界的舞台上,操作系统扮演着导演的角色,精心安排着每一个进程的表演。本文将揭开进程管理的神秘面纱,从进程的诞生到终结,探究它们如何在操作系统的指挥下和谐共舞。通过生动的比喻和直观的代码示例,我们将一同走进操作系统的核心,理解进程调度、同步与通信的内在机制,以及它们对计算生态的重要性。让我们跟随代码的节奏,一起感受操作系统的魅力吧!
|
1月前
|
消息中间件 存储 监控
Java并发知识之ReentrantLock
本文深入剖析了Java中并发编程的核心概念,特别聚焦于锁的设计思想,通过分析AbstractQueuedSynchronizer(AQS)、ReentrantLock和ReentrantReadWriteLock的实现,揭示了锁的工作原理和高效并发控制策略。
Java并发知识之ReentrantLock
|
18天前
|
Java 开发者
Java多线程教程:使用ReentrantLock实现高级锁功能
Java多线程教程:使用ReentrantLock实现高级锁功能
21 1
|
20天前
|
Java
Java 并发编程:理解并应用 ReentrantLock
【7月更文挑战第56天】 在多线程环境下,为了保证数据一致性和程序正确性,我们需要对共享资源进行同步访问。Java提供了多种并发工具来帮助我们实现这一目标,其中ReentrantLock是一个功能强大且灵活的同步机制。本文将深入探讨ReentrantLock的基本原理、使用方法以及与synchronized关键字的区别,帮助读者更好地理解和应用这一重要的并发编程工具。
|
14天前
|
传感器 C# 监控
硬件交互新体验:WPF与传感器的完美结合——从初始化串行端口到读取温度数据,一步步教你打造实时监控的智能应用
【8月更文挑战第31天】本文通过详细教程,指导Windows Presentation Foundation (WPF) 开发者如何读取并处理温度传感器数据,增强应用程序的功能性和用户体验。首先,通过`.NET Framework`的`Serial Port`类实现与传感器的串行通信;接着,创建WPF界面显示实时数据;最后,提供示例代码说明如何初始化串行端口及读取数据。无论哪种传感器,只要支持串行通信,均可采用类似方法集成到WPF应用中。适合希望掌握硬件交互技术的WPF开发者参考。
34 0
|
14天前
|
安全 Java
Java并发编程实战:使用synchronized和ReentrantLock实现线程安全
【8月更文挑战第31天】在Java并发编程中,保证线程安全是至关重要的。本文将通过对比synchronized和ReentrantLock两种锁机制,深入探讨它们在实现线程安全方面的优缺点,并通过代码示例展示如何使用这两种锁来保护共享资源。
|
17天前
|
存储 安全 Java
深入理解操作系统:从用户空间到内核空间的旅程深入浅出Java异常处理机制
【8月更文挑战第28天】在数字世界的海洋中,操作系统是承载软件与硬件沟通的巨轮。本文将揭开操作系统神秘的面纱,通过一次思维的航行,带领读者从应用程序的用户空间出发,穿越系统调用的大门,深入内核空间的心脏。我们将探索进程管理、内存分配、文件系统等核心概念,并借助代码示例,揭示操作系统背后的魔法。准备好了吗?让我们启航,去发现那些隐藏在日常计算活动背后的秘密。 【8月更文挑战第28天】在Java编程世界中,异常处理就像是我们生活中的急救包。它不仅保护程序不因意外而崩溃,还确保了代码的健壮性和可靠性。本文将通过简洁明了的语言和生动的比喻,带你了解Java异常处理的奥秘,从基本的try-catch语
|
21天前
|
Java C++
【Java 并发秘籍】synchronized vs ReentrantLock:揭秘线程同步神器的对决!
【8月更文挑战第24天】本文详细对比了Java并发编程中`synchronized`关键字与`ReentrantLock`的不同之处。`synchronized`作为内置关键字,提供自动锁管理但不支持中断或公平锁;`ReentrantLock`则通过显式调用方法控制锁,具备更多高级功能如可中断、公平锁及条件变量。文章通过两个计数器类实例展示了两种机制的具体应用,帮助读者理解其差异及适用场景。掌握这两者对于提升多线程程序设计能力至关重要。
33 0