java对管程的支持

管程的概念

管程 (英语:Moniters,也称为监视器) 是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源。

这些共享资源一般是硬件设备或一群变量。管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。

与那些通过修改数据结构实现互斥访问的并发程序设计相比,管程实现很大程度上简化了程序设计。

管程提供了一种机制,线程可以临时放弃互斥访问,等待某些条件得到满足后,重新获得执行权恢复它的互斥访问。

一个管程包含:

  1. 多个彼此可以交互并共用资源的线程
  2. 多个与资源使用有关的变量
  3. 一个互斥锁
  4. 一个用来避免竞态条件的不变量

一个管程的程序在运行一个线程前会先取得互斥锁,直到完成线程或是线程等待某个条件被满足才会放弃互斥锁。

若每个执行中的线程在放弃互斥锁之前都能保证不变量成立,则所有线程皆不会导致竞态条件成立。

当一个线程执行管程中的一个子程序时,称为占用(occupy)该管程. 管程的实现确保了在一个时间点,最多只有一个线程占用了该管程。这是管程的互斥锁访问性质。

当线程要调用一个定义在管程中的子程序时,必须等到已经没有其它线程在执行管程中的某个子程序。

在管程的简单实现中,编译器为每个管程对象自动加入一把私有的互斥锁。该互斥锁初始状态为解锁,

在管程的每个公共子程序的入口给该互斥锁加锁,在管程的每个公共子程序的出口给该互斥锁解锁。

条件变量(Condition Variable)

管程提供了一种实现互斥的简便途径,但这还不够。我们还需要一种办法使得线程在无法继续运行时被阻塞。

在生产者-消费者问题中,很容易将针对缓冲区满和缓冲区空的测试放到管程过程中,但是生产者在发现缓冲区满的时候如何阻塞呢?

解决的方法是引入条件变量(condition variables)以及相关的两个操作:wait和signal。

当一个管程过程发现它无法继续运行时(例如,生产者发现缓冲区满),它会在某个条件变量上(如full)执行wait操作。

该操作导致调用进程自身阻塞,并且还将另一个以前等在管程之外的进程调入管程。

另一个线程,比如消费者,可以唤醒正在睡眠的伙伴进程,这可以通过对其伙伴正在等待的一个条件变量执行signal完成。

为了避免管程中同时有两个活跃进程,如果在一个条件变量上有若干进程正在等待,则在对该条件变量执行signal操作后,

系统调度程序只能在其中选择一个使其恢复运行。

线程可能需要等待某个条件P为真,才能继续执行。在一个忙等待(busy waiting)循环中

while not( P ) do skip

将会导致所有其它进程都无法进入临界区使得该条件P为真,该管程发生死锁.

解决办法是条件变量(condition variables). 概念上,一个条件变量就是一个线程队列(queue), 其中的线程正等待某个条件变为真。

每个条件变量c关联着一个断言P_c. 当一个线程等待一个条件变量,该线程不算作占用了该管程,因而其它线程可以进入该管程执行,

改变管程的状态,通知条件变量c其关联的断言P_c在当前状态下为真.

因此对条件变量存在两种主要操作:

wait c 被一个线程调用,以等待断言P_c被满足后该线程可恢复执行. 线程挂在该条件变量上等待时,不被认为是占用了管程.

signal c (有时写作notify c)被一个线程调用,以指出断言P_c现在为真.

当一个通知(signal)发给了一个有线程处于等待中的条件变量,则有至少两个线程将要占用该管程: 发出通知的线程与等待该通知的某个线程.

只能有一个线程占用该管程,因此必须做出选择。两种理论体系导致了两种不同的条件变量的实现:

阻塞式条件变量(Blocking condition variables),把优先级给了被通知的线程.

非阻塞式条件变量(Nonblocking condition variables),把优先级给了发出通知的线程.

隐式条件变量管程

Java程序设计语言中,每个对象都可以作为一个管程。需要互斥使用的方法必须明确标示关键字synchronized. 代码块也可以标示关键字synchronized.

不使用明确的条件变量, Java的这种管程在入口队列之外,使用单独的条件等待队列. 所有等待的线程进入这个队列,

所有的notify与notify all操作也施加于这个队列。这种方法已经被其它程序设计语言使用,如C#.

Java中的同步方法与其他经典管程有本质差别:Java没有内嵌的条件变量。反之,Java提供了两个过程wait和notify ,分别与sleep和wakeup等价,

不过,当它们在同步方法中使用时,它们不受竞争条件约束。理论上,方法wait可以被中断,它本身就是与中断有关的代码。Java需要显式表示异常处理。在下文中,只要认为go_to_sleep就是去睡眠即可。

java管程解决生产者-消费者问题

外部类(outer class)ProducerConsumer创建并启动两个线程,p和c。第二个类和第三个类producer和consumer分别包含生产者和消费者的代码。

最后,类our_monitor是管程,它有两个同步线程,用于在共享缓冲区中插入和取出数据项。

public class ProducerConsumer {
	static final int N = 100;// 定义缓冲区大小的常量
	static Producer p = new Producer();// 初始化一个新的生产者线程
	static Consumer c = new Consumer();// 初始化一个新的消费者线程
	static Our_monitor mon = new Our_monitor();// 初始化一个新的管程

	public static void main(String[] args) {
		p.start();// 生产者启动
		c.start();// 消费者启动
	}

	static class Producer extends Thread {
		public void run() {// 线程运行主代码
			int item;
			while (true) {// 生产者循环
				item = produce_item();
				mon.insert(item);
			}
		}

		private int produce_item() {
			System.out.println("生产了1个");
			return 1;
		}// 实际生产
	}

	static class Consumer extends Thread {
		public void run() {// 线程运行主代码
			int item;
			while (true) {// 消费者循环
				item = mon.remove();
				consume_item(item);
			}
		}

		private void consume_item(int item) {
			System.out.println("消费了1个");
		}// 实际消费
	}

	static class Our_monitor {// 管程
		private int buffer[] = new int[N];
		private int count = 0, lo = 0, hi = 0;// 计数器和索引

		public synchronized void insert(int val) {
			if (count == N)
				go_to_sleep();// 若缓冲区满,则进入睡眠
			buffer[hi] = val;// 向缓冲区中插入一个新的数据项
			hi = (hi + 1) % N;// 设置下一个数据项的槽
			count = count + 1;// 缓冲区的数据项又多了一项
			if (count == 1)
				notify();// 如果消费者在休眠,则将其唤醒
		}

		public synchronized int remove() {
			int val;
			if (count == 0)
				go_to_sleep();// 如果缓冲区空,进入休眠
			val = buffer[lo];// 从缓冲区中取出一个数据项
			lo = (lo + 1) % N;// 设置待取数据项的槽
			count = count - 1;// 缓冲区的数据数目减少1
			if (count == N - 1)
				notify();// 如果生产者正在休眠,则将其唤醒
			return val;
		}

		private void go_to_sleep() {
			try {wait();} catch (InterruptedException exc) {};
		}
	}
}

该程序中比较意思的部分是类our_monitor,它包含缓冲区、管理变量以及两个同步方法。当生产者在insert内活动时,它确信消费者不能在remove中活动,从而保证更新变量和缓冲区的安全,且不用担心竞争条件。变量count记录在缓冲区中数据项的数量。它的取值可以取从0到N-1之间任何值。变量lo是缓冲区槽的序号,指出将要取出的下一个数据项。类似地,hi是缓冲区中下一个将要放入的数据项序号。允许 lo = hi,其含义是在缓冲区中有0个或N个数据项。count的值说明了究竟是哪一种情形。

参考文献:

http://zh.wikipedia.org/zh-cn/%E7%9B%A3%E8%A6%96%E5%99%A8_(%E7%A8%8B%E5%BA%8F%E5%90%8C%E6%AD%A5%E5%8C%96)

现代操作系统

时间: 2024-07-30 19:30:54

java对管程的支持的相关文章

java阻塞主线程的实现

1.声明计数器线程个数: CountDownLatch latch= new CountDownLatch(2); 2.线程启动带参数latch: Worker worker1= new Worker("zhang san" , 5000, latch); 3.线程启动,主线程阻塞: worker1.start(); latch.await(); 4.线程run()方法中计数器技术开始: public void run(){ System. out.println("Work

java开源内容管理系统J4CMS支持真正静态化

原理很简单,使用httpclient请求遍历整个网站的菜单.文章链接,请求下来以后,生成html文件.即静态化了 把它们稍作调整,直接扔在88元购买的阿里云主机上,网站就完成了 这是我的 静态网站 http://www.j4cms.com/ 这是我的演示网站 http://java4cms.jd-app.com/index.html 是不是一模一样,这样一来,完全可以在本地抛起J4CMS 完成内容的编辑,一键生成html 用 ftp 直接传到自己的空间,就不用再话大价钱买java 空间,用死慢的

Java操作属性文件,支持新增或更新多个属性

Java操作属性文件,支持新增或更新多个属性 一.更新或新增单个属性的方法 /** * 写入properties信息 * @param filePath 绝对路径(包括文件名和后缀名) * @param parameterName 名称 * @param parameterValue 值 */ public static void writeProperties(String filePath, String parameterName, String parameterValue) { Pro

Java之协程(quasar)

一.前面我们简单的说了一下,Python中的协程原理.这里补充Java的协程实现过程.有需要可以查看python之协程. 二.Java协程,其实做Java这么久我也没有怎么听过Java协程的东西,但是一直有有听到微线程/协程的概念,这不在学习Python的时候接触到了协程一词.然后返回来去了解Java的协程问题,但是看了很多资料,发现官网以及很多地方都没有涉及到协程的东西,没有办法,只能通过强大的社区来学习协程的相关东西. 三.这里主要关注的是:quasar. 1)协程的目的:当我们在使用多线程

Java的纤程库 - Quasar

最近遇到的一个问题大概是微服务架构中经常会遇到的一个问题: 服务 A 是我们开发的系统,它的业务需要调用 B . C . D 等多个服务,这些服务是通过http的访问提供的. 问题是 B . C . D 这些服务都是第三方提供的,不能保证它们的响应时间,快的话十几毫秒,慢的话甚至1秒多,所以这些服务的Latency比较长.幸运地是这些服务都是集群部署的,容错率和并发支持都比较高,所以不担心它们的并发性能,唯一不爽的就是就是它们的Latency太高了. 系统A会从Client接收Request,

Java实现主线程等待子线程

本文介绍两种主线程等待子线程的实现方式,以5个子线程来说明: 1.使用Thread的join()方法,join()方法会阻塞主线程继续向下执行. 2.使用Java.util.concurrent中的CountDownLatch,是一个倒数计数器.初始化时先设置一个倒数计数初始值,每调用一次countDown()方法,倒数值减一,他的await()方法会阻塞当前进程,直到倒数至0. join方式代码如下: [java] view plain copy package com.test.thread

edtftpj让Java上传FTP文件支持断点续传

在用Java实现FTP上传文件功能时,特别是上传大文件的时候,可以需要这样的功能:程序在上传的过程中意外终止了,文件传了一大半,想从断掉了地方继续传:或者想做类似迅雷下载类似的功能,文件太大,今天传一半,睡一觉去先,明天继续传. Java上传FTP文件,用的比较多的工具是apache的commons-net.如果想用commons-net实现FTP上传的断点续传还是有点麻烦. 除了commons-net之外,还有很多非常优秀的FTP工具,这里使用edtftpj这个工具来实现断点续传. 下载:ht

java properties 对list的支持

经测试,原生的properties 对 list 不支持. 参考样例如下: id=1 id=2 具体代码如下: 1 java.util.Properties prop = new Properties(); 2 InputStream is = App.class.getResourceAsStream("/config.properties"); 3 if(is == null){ 4 System.out.println("is is null"); 5 } 6

Maven Java项目添加Scala语言支持

为了在一个普通的使用Maven构建的Java项目中,增加对Scala语言的支持.使得其能够同时编译Java和Scala语言的文件.其实很简单的一件事情,只需要在pom.xml文件中的build部分中的plugins中,增加对scala-maven-plugin的支持,就好了.具体的说,就是添加如下的语句: <plugin> <!-- see http://davidb.github.com/scala-maven-plugin --> <groupId>net.alch