操作系统作业睡觉助教(用Java的ReentrantLock实现)
Posted Dreamchaser追梦
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了操作系统作业睡觉助教(用Java的ReentrantLock实现)相关的知识,希望对你有一定的参考价值。
文章目录
前言
很多时候我在思考什么是多线程,什么是多进程,开始学的的时候我天真的以为我懂了,可后来发现我只是管中窥豹而已。包括现在,我也只是初入多线程而已。所以作为初学者的我并不能说此文的思路是最优解,本文只是我在学习过程中想到的一个可行方案。读者不必纠结于某些地方为什么这样,因为很可能你的想法比我的更好,当然欢迎各位在评论去提出的想法和建议。
在阅读本文之前,我想先明确一个问题——Java和操作系统多线程之间的联系是什么?为了更清楚理解这个问题,我建议你阅读Java线程和操作系统线程的关系这篇文章,这对于你理解Java中的一些并发操作将大有裨益。
你要记住,所谓多线程/多进程是操作系统层面上的概念,而不是语言层面的概念,而Java在jdk1.2之后的版本中,多线程的实现皆依赖于操作系统的接口。
一、题目
睡觉助教
某大学计算机科学系有一名助教(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.运行效果截图
三、思路二(思路优化)
原本博文只有思路一的,但那时我便发现之前的代码并不规范,逻辑也不太清晰,而随着学习深入我对之前的思路进行优化,使代码更加简单规范,遂有了思路二。
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.运行结果截图
结语
以上是我的实现思路。希望我的思路能个各位有所帮助,当然,如果有什么意见或者建议的,欢迎在评论区评论。
最后,
愿我们以梦为马,不负青春韶华!
与君共勉!
以上是关于操作系统作业睡觉助教(用Java的ReentrantLock实现)的主要内容,如果未能解决你的问题,请参考以下文章