共享受限资源,解决共享资源竞争,Brian的同步规则

本文目录

一、共享受限资源

二、解决共享资源竞争

2.1 使用synchronized关键字

2.2 显示的使用Lock锁

三、Brian的同步规则


一、共享受限资源

可以把单线程程序当作在问题域求解的单一实体,每次只能做一件事情。因为只有一个实体,所以永远不用担心诸如“两个实体试图同时使用同一个资源”这样的问题。比如,两个人在同一个地方停车,两个人同时走过一扇门,甚至是两个人同时说话。

有了并发就可以同时做多件事情了,但是,两个或多个线程彼此互相干涉的问题也就出现了。如果不防范这种冲突,就可能发生两个线程同时试图访问同一个银行账户,或向同一个打印机打印,改变同一个值等诸如此类的问题。

二、解决共享资源竞争

基本上所有的并发模式在解决线程冲突问题的时候,都是采用序列化访问共享资源的方案。这意味着在给定时刻只允许一个任务访问共享资源。通常这是通过在代码前面加上一条锁语句来实现的,这就使得在一段时间内只有一个任务可以运行这段代码。因为锁语句产生了一种互相排斥的效果,所以这种机制常常称为互斥量(mutex)。

2.1 使用synchronized关键字

Java以提供 synchronized关键字的形式,为防止资源冲突提供了内置支持。当任务要执行被synchronized关键字保护的代码片段的时候,它将检查锁是否可用,然后获取锁,执行代码,释放锁。

共享资源一般是以对象形式存在的内存片段,但也可以是文件、输入/输出端口,或者是打印机。要控制对共享资源的访问,得先把它包装进一个对象。然后把所有要访问这个资源的方法标记为 synchronized。如果某个任务处于一个对标记为 synchronized的方法的调用中,那么在这个线程从该方法返回之前,其他所有要调用类中任何标记为 synchronized方法的线程都会被阻塞。

所有对象都自动含有单一的锁(也称为监视器)。当在对象上调用其任意 synchronized方法的时候,此对象都被加锁,这时该对象上的其他 synchronized方法只有等到前一个方法调用完毕并释放了锁之后才能被调用。

一个任务可以多次获得对象的锁。如果一个方法在同一个对象上调用了第二个方法,后者又调用了同一对象上的另一个方法,就会发生这种情况。JVM负责跟踪对象被加锁的次数。如果一个对象被解锁(即锁被完全释放),其计数变为0。在任务第一次给对象加锁的时候,计数变为1。每当这个相同的任务在这个对象上获得锁时,计数都会递增。显然,只有首先获得了锁的任务才能允许继续获取多个锁。每当任务离开一个 synchronized方法,计数递减,当计数为零的时候,锁被完全释放,此时别的任务就可以使用此资源。针对每个类,也有一个锁(作为类的Class象的一部分);所以 synchronized static方法可以在类的范围内防止对 static数据的并发访问。

2.2 显示的使用Lock锁

Java SE5的java.util. concurrent类库还包含有定义在 java util. concurrent. locks中的显式的互斥机制。Lock对象必须被显式地创建、锁定和释放。因此,它与内建的锁形式相比,代码缺乏优雅性。但是,对于解决某些类型的问题来说,它更加灵活。

尽管try- -finally所需的代码比 synchronized关键字要多,但是这也代表了显式的Lock对象的优点之一。如果在使用synchronized关键字时,某些事物失败了,那么就会抛出一个异常。但是你没有机会去做任何清理工作,以维护系统使其处于良好状态。有了显式的Lock对象,你就可以使用 finally子句将系统维护在正确的状态了。

大体上,当你使用 synchronized关键字时,需要写的代码量更少,并且用户错误出现的可能性也会降低,因此通常只有在解决特殊问题时,才使用显式的Lock对象。例如,用synchronized关键字不能尝试着获取锁且最终获取锁会失败,或者尝试着获取锁一段时间,然后放弃它,要实现这些,你必须使用 concurrent类库。

显式的Lock对象在加锁和释放锁方面,相对于内建的 synchronized锁来说,还赋予了你更细粒度的控制力。这对于实现专有同步结构是很有用的,例如用于遍历链接列表中的节点的节节传递的加锁机制(也称为锁耦合),这种遍历代码必须在释放当前节点的锁之前捕获下一个节点的锁。

三、Brian的同步规则

应该什么时候同步呢?可以运用 Brian的同步规则:

如果你正在写一个变量,它可能接下来将被另一个线程读取,或者正在读取一个上一次已经被另一个线程写过的变量,那么你必须使用同步,并且,读写线程都必须用相同的监视器锁同步。

如果在你的类中有超过一个方法在处理临界数据,那么你必须同步所有相关的方法。如果只同步一个方法,那么其他方法将会随意地忽略这个对象锁,并可以在无任何惩罚的情况下被调用。这是很重要的一点:每个访问临界共享资源的方法都必须被同步,否则它们就不会正确地工作。

注意: Java递增不是原子性操作。

 

【参考资料】

Thinking in Java(Java编程思想)第四版

原文地址:https://www.cnblogs.com/no8g/p/13415491.html