java 监视锁_Java 并发:第五部分 - 监视器(锁与条件锁)

在了解了如何使用信号同步代码之后,我们来了解如何使用监控器来做到这壹点。监视器在并发编程中是另外壹种方法。它是比信号更强大更高层次的方法。监视器是壹個类的实例,它可以安全的用于多個线程中。所有监控器中的方法都是互斥的执行的。所以任意同壹时刻最多只有壹个线程可以在监控器中执行。这种互斥政策使得在监控器下运行和开发更加容易。

监视器有個额外的特性,就是有可能让壹個线程等待某种条件。在等待的这段时间内,线程临时放弃它独有的访问控制,并且壹定会在条件满足之后再次获取这個访问控制权。你也可以同时标识壹個或者多個线程的条件得到了满足。使用监视器比使用低水平方法要多几個好处:

1、所有的同步代码集中在壹個地方,并且这段代码的用户不需要知道它是如何实现的;

2、代码不依赖于进程的数量,你希望它为多少個进程工作,它就可以为多少個进程工作;

3、你不需要释放任何东西(比如说信号量),所以你不会忘记去这么做。

当我们需要描述壹個监视器时,我们只是简单的使用监视视的关键词,并且像描述普通方法壹样描述它:

monitor SimpleMonitor {

public method void testA(){

//壹些代码

}

public method int testB(){

return 1;

}

} 为了描述壹种状态下的变量,我们使用条件关键字。条件关键字是壹种正在等待相同的条件下过程的队列。在壹种条件下你可以拥有多個操作变量,最重要的变量是在某种条件下,标识过程等待将会被唤醒或者进入等待状态的那壹個。在信号/等待操作和PV信号量中有壹些相似之处,但是也有壹些细小的差异。如果队列为空,则信号操作什么都不做,等待操作则总是把线程放入等待队列中。处理队列执行先进先出操作模式。当壹個线程在等待某种条件时被唤醒,它必须再次获取锁才能继续在代码中执行。

在继续之前,我们必须了解更多关于信号操作的信息。当编写监控器代码时,你通常要在几個关于信号操作的观点中作出选择:

1、信号与继续(SC):相互排斥的信号将被唤醒,但是在执行之前需要掌握相互排斥的条件;

2、信号与等待(SW):信号被屏蔽,必须等待相互排斥才能继续,信号线程直接被唤醒可以继续执行它的操作;

3、信号与急切等待(SU):就像SW壹样,但是信号线程有保证,它将会在信号线程执行之后马上执行;

4、信号与退出(SX):当信号从方法中直接退出时,信号线程可以直接开始。这個观念并不常用。 允许使用的策略依赖于编程的语言,在 Java 中,只有壹個策略可用,那就是 SC 这壹种。

在 Java 中没有直接创建监控器的关键字,你必须创建壹個新的类并且使用

Lock 和

Condition 类。

Lock 是壹個接口,

ReentrantLock 则是它的壹個主要的实现,我们这节课程将要学习的壹個知识点就是它。为了创建壹個 ReentrantLock ,你有两個构造方法可以使用,壹個默认的构造方法和壹個带有布尔型参数(说明这個锁是否公平)的构造方法。公平锁表明线程会按照它们请求的顺序获取锁。公平锁比默认锁策略占的比重稍大,所以如果你需要那就去使用它。为了获得锁,你只需要需要壹個方法锁并且释放它。

明确的锁比同步块具有相同的内存语义。

所以当您使用lock() 或者 unlock()块时能见度的变化是保证。

所以为了实现这壹点,我们之前看过的监控器样例需要创建壹個类并且使用锁来实现互斥:

public class SimpleMonitor {

private final Lock lock = new ReentrantLock();

public void testA() {

lock.lock();

try {

//Some code

} finally {

lock.unlock();

}

}

public int testB() {

lock.lock();

try {

return 1;

} finally {

lock.unlock();

}

}

}

那些已经阅读过本系列文章的其它章节的人会说,如果在这两個方法上使用 synchronized 关键字会更加简单,我们可以不用条件变量。 在锁上使用 newCondition() 方法可以让你创建新的条件。条件就是壹类条件的变量,你可以使用 await() 方法让当前线程等待条件直到满足为止(并且伴随着不同的超时时间),你也可以使用 signal() 或者 signalAll() 方法唤醒线程。方法 signalAll() 唤醒在此条件变量下等待的所有线程。

现在让我们尝试壹個简单的常见样例:受限缓冲区。它是壹個带有开始和结束的周期性缓冲区。

import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

public class BoundedBuffer {

private final String[] buffer;

private final int capacity;

private int front;

private int rear;

private int count;

private final Lock lock = new ReentrantLock();

private final Condition notFull = lock.newCondition();

private final Condition notEmpty = lock.newCondition();

public BoundedBuffer(int capacity) {

super();

this.capacity = capacity;

buffer = new String[capacity];

}

public void deposit(String data) throws InterruptedException {

lock.lock();

try {

while (count == capacity) {

notFull.await();

}

buffer[rear] = data;

rear = (rear + 1) % capacity;

count++;

notEmpty.signal();

} finally {

lock.unlock();

}

}

public String fetch() throws InterruptedException {

lock.lock();

try {

while (count == 0) {

notEmpty.await();

}

String result = buffer[front];

front = (front + 1) % capacity;

count--;

notFull.signal();

return result;

} finally {

lock.unlock();

}

}

}

另外这里有壹些说明:

1、两個方法使用 protected 关键字是为了确保信号互斥。

2、然后我们使用了两個条件变量。壹個确保缓冲区不会空,另外壹個确保缓冲区不会满。

3、你会注意到我把等待操作放到了壹個 while 循环中。这是为了防止线程在等待和继续的过程中发生信号偷窃者问题。

这样壹来我们就可以在几個线程中放心的使用 BoundedBuffer 而不会出现问题。

就像你看到的壹样,你可以使用监控器来解决很多并发编程中的难题,并且这种方法确实非常强大且高效。

我希望你对这篇文章感兴趣,并且希望这壹系列文章能够带来壹些关于 Java 的读者朋友。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值