格言:在程序猿界混出点名堂!
![](https://img.haomeiwen.com/i18759109/f022812d61509771.png)
《JAVA并发编程实战》解读
【连载】第2章-2.4用锁来保护状态
回顾:上一节主要介绍synchronized关键字和重入锁,这一节分享的是用锁来保护状态。
由于锁能使其保护的代码路径以串行形式
访问,因此通过锁来构造一些协议
以实现对共享状态的独占访问。
上边是原文的一段话,今天主要谈谈以下几点内容:
协议
题外彩蛋:
协议可理解对加锁范围的一种约定,也是对复合操作的一种束缚。
其实这种约定主要约定了哪些操作为一个原子操作,原子操作的范围是什么?比如上一节的因数分解的例子:
public synchronized void service(ServletRequest req, ServletResponse resp){
BigInteger i = extractFromRequest(req);
if(i.equals(lastNumber.get())){
// 如果缓存过,直接返回结果
encodeIntoResponse(resp, lastFactors.get());
}else{
// 没有缓存才计算
BigInteger[] factors = factor(i);
lastNumer.set(i);
lastFactors.set(factors);
encodeIntoResponse(resp, lastFactors.get());
}
这种约束就约束到整个Servlet的访问范围。但同时我可以把更新last操作作为原子范围作为一种协议:
public void service(ServletRequest req, ServletResponse resp){
BigInteger i = extractFromRequest(req);
if(i.equals(lastNumber.get())){
// 如果缓存过,直接返回结果
encodeIntoResponse(resp, lastFactors.get());
}else{
// 没有缓存才计算
BigInteger[] factors = factor(i);
synchronized(this){
lastNumer.set(i);
lastFactors.set(factors);
}
encodeIntoResponse(resp, lastFactors.get());
}
你或许认为上面的代码比第一种写法效率更高,但是第二种写法仍然存在问题,这就是我所说的第二点。
读写同锁保证原子性
上面的代码我们只在写的时候加锁,保证写的原子性,试想如果一个线程set了lastNumer,但没有set lastFactors,另外一个线程可能返回错误的值。
因此,读写同锁保证原子性。
但上述代码在业务代码中,我们可能会考虑进行简单封装,然后调用:
public synchronized void setLast(BigInteger ,BigInteger[] factors){
...
}
public synchronized BigInteger[] getLast(BigInteger i){
return ...;
}
可能细心的小伙伴已经看出来了,好像一个map的使用,这就是我们要讲的第三点:
对象内部封装可变状态
一种常见的加锁约定是,将所有的可变状态封装在对象的内部。
比如我们知道的Collection下面的实现,Vector或者HashTable等。上面的代码我们进行改造试试看,小编记事本编写,有问题欢迎评论区留言:
@ThreadSafe
public class SafeCachingFactorizer implements Servlet{
// 上一次计算的数字
private final Map<BigInteger,BigInteger[]> last = new HashTable<BigInteger,BigInteger[]>();
public void service(ServletRequest req, ServletResponse resp){
BigInteger i = extractFromRequest(req);
BigInteger[] factors;
if((factors= last.get(i))!=null){
// 如果缓存过,直接返回结果
encodeIntoResponse(resp, factors);
}else{
// 没有缓存才计算
factors = factor(i);
last.put(i,factors);
encodeIntoResponse(resp, factors);
}
}
}
为什么不在所有方法都加上synchronized呢?
- 如果所有方法都可以加,我们今天就没有机会在这里研究和讨论这个话题了😄
- 性能考虑,如果所有方法都是同步,就造成该对象所有方法串行执行。
- 活跃性问题,之前章节讲过活跃性问题,会造成死锁等。后面章节会单独解说死锁。
Vector一定不存在竞态条件?
答案:未必,据协议约定的原子范畴而定。
if(!vector.contains(element)){
vector.add(element);
}
contains和add本身是原子的,但这个复合操作就是非原子的。
喜欢连载可关注
简书
或者微信公众号
:
简书专题:Java并发编程实战-可爱猪猪解读
https://www.jianshu.com/c/ac717321a386
微信公众号:可爱猪猪聊程序
。
![]()
网友评论