2.4. Guarding State with Locks(使用锁维护状态)

2.4. Guarding State with Locks(使用锁维护状态)
Because locks enable serialized [8] access to the code paths they guard, we can use them to construct protocols for guaranteeing exclusive access to shared state. Following these protocols consistently can ensure state consistency.
由于锁机制可以保证对他们所保护的代码路径的序列化访问,我们可以使用锁机制来建立保证对共享状态的独占式访问。只要遵循这些协议,就可以保证状态的一致性。
[8] Serializing access to an object has nothing to do with object serialization (turning an object into a byte stream); serializing access means that threads take turns accessing the object exclusively, rather than doing so concurrently.
序列化的访问一个类与类的序列化没有一点儿关系(把一个类变成字节流),序列化的访问意味着线程独占式的轮流访问对象,而不是并发的访问。
Compound actions on shared state, such as incrementing a hit counter (read-modify-write) or lazy initialization (check-then-act), must be made atomic to avoid race conditions. Holding a lock for the entire duration of a compound action can make that compound action atomic. However, just wrapping the compound action with a synchronized block is not sufficient; if synchronization is used to coordinate access to a variable, it is needed everywhere that variable is accessed. Further, when using locks to coordinate access to a variable, the same lock must be used wherever that variable is accessed.
共享状态的复合行为,比如增加一个“点击计数”、或者延迟初始化,都必须被设置成原子化的来避免条件竞争。对整个复合行为加上锁机制,就可以把复合锁变成原子的。但是,仅仅把复合行为加上锁机制还是不够的,如果同步机制被用来协调访问一个可变因素,那么在可变因素可以被访问的任何地方,同步机制都必须存在。进一步说,当使用锁来管理对可变因素的访问的时候,那么这把锁就应该应用在任何可变因素可以被访问的地方。
It is a common mistake to assume that synchronization needs to be used only when writing to shared variables; this is simply not true. (The reasons for this will become clearer in Section 3.1.)
有一个非常普遍的错误认为只有在写入共享变量的时候,同步机制才是必须的,这种想法是错误(错误的原因在第3.1节中)。
For each mutable state variable that may be accessed by more than one thread, all accesses to that variable must be performed with the same lock held. In this case, we say that the variable is guarded by that lock.
由于每个可变的状态变量可能会被多个线程访问,所有对变量的访问都必须被同一把锁管理。在这种情况下,我们说变量被那把锁守护。

In SynchronizedFactorizer in Listing 2.6, lastNumber and lastFactors are guarded by the servlet object's intrinsic lock; this is documented by the @GuardedBy annotation.
在SynchronizedFactorizer中,lastNumber和lastFactors被servlet对象的内在锁守护。
There is no inherent relationship between an object's intrinsic lock and its state; an object's fields need not be guarded by its intrinsic lock, though this is a perfectly valid locking convention that is used by many classes. Acquiring the lock associated with an object does not prevent other threads from accessing that object the only thing that acquiring a lock prevents any other thread from doing is acquiring that same lock. The fact that every object has a built-in lock is just a convenience so that you needn't explicitly create lock objects. [9] It is up to you to construct locking protocols or synchronization policies that let you access shared state safely, and to use them consistently throughout your program.
[9] In retrospect, this design decision was probably a bad one: not only can it be confusing, but it forces JVM implementors to make tradeoffs between object size and locking performance.
在对象的内在锁和状态之间没有内在的联系。一个对象的状态不是必须被他本身的内在锁守护,尽管这种保护方式是一种被很多类所使用的正确方式。拥有与对象关联的锁并不会阻止其他线程访问该对象,拥有锁后唯一能够确保的事情是可以组织其余线程去获取锁。每个对象都有一把内在锁仅仅是为了让你不必去显示的创建一个锁对象。现在你可以来创建锁协议或者同步策略来安全的共享状态了,并且你要在你的整个程序中持续使用。
回顾起来,这个设计决定是非常糟糕的,这不仅容易让人迷惑,而且使得JVM的实现者不得不在对象尺寸和锁的性能上作出取舍。
Every shared, mutable variable should be guarded by exactly one lock. Make it clear to maintainers which lock that is.
每一个共享的,可变的变量都应该被一把锁来守护。应该对维护者说明使用那把锁。
A common locking convention is to encapsulate all mutable state within an object and to protect it from concurrent access by synchronizing any code path that accesses mutable state using the object's intrinsic lock. This pattern is used by many thread-safe classes, such as Vector and other synchronized collection classes. In such cases, all the variables in an object's state are guarded by the object's intrinsic lock. However, there is nothing special about this pattern, and neither the compiler nor the runtime enforces this (or any other) pattern of locking. [10] It is also easy to subvert this locking protocol accidentally by adding a new method or code path and forgetting to use synchronization.
一个常见的锁机制使用惯例是把所有的可变状态封装入一个对象中,然后通过对象的内在锁来同步对可变状态访问来保护并发访问。这种方法被很多现成安全的类使用,比如Vector,和其他同步容器类。这些场景中,一个对象中所有状态都被对象的内在锁保护。但是,这种模式并没有特别之处,编译器和运行时环境都不会强制使用这种方式。通过增加一个新的方法或者代码或者忘记使用synchronized关键都很容易破坏这种锁协议。
[10] Code auditing tools like FindBugs can identify when a variable is frequently but not always accessed with a lock held, which may indicate a bug.
向FindBugs这样的代码审查工具会检查出一个频繁被锁保护,但是不总是被锁保护的变量。因为这通常意味着一个bug。
Not all data needs to be guarded by locks only mutable data that will be accessed from multiple threads. In Chapter 1, we described how adding a simple asynchronous event such as a TimerTask can create thread safety requirements that ripple throughout your program, especially if your program state is poorly encapsulated. Consider a single-threaded program that processes a large amount of data. Single-threaded programs require no synchronization, because no data is shared across threads. Now imagine you want to add a feature to create periodic snapshots of its progress, so that it does not have to start again from the beginning if it crashes or must be stopped. You might choose to do this with a TimerTask that goes off every ten minutes, saving the program state to a file.
并不是所有的数据都需要被锁守护,只有可变的、而且会被多线程访问的数据才需要。在第一章中,我们描述了如果在你的应用程序中加入一个简单的异步事务(比如TimerTask),可能会导致你的整个程序面临着线程安全的需求。尤其是,当你的程序状态封装的很差的话。单线程的应用程序不需要同步,因为没有数据在线程间共享。现在想象一下,如果你想创建一个获得进程周期性快照的特性,这样在进程失败或者被停止的情况下,你就不必重新开始。你可以选择使用一个每隔十分钟进行一次的TimerTask来进行,并且把程序状态放入文件。
Since the TimerTask will be called from another thread (one managed by Timer), any data involved in the snapshot is now accessed by two threads: the main program thread and the Timer tHRead. This means that not only must the TimerTask code use synchronization when accessing the program state, but so must any code path in the rest of the program that touches that same data. What used to require no synchronization now requires synchronization throughout the program.
由于TimerTask可能会被其他线程调用(或者被Timer管理),所有涉及到快照的数据都有可能会被两个线程访问:主线程和Timer线程。这就意味着不仅TimerTask访问程序状态的代码需要同步机制,原来程序关联到相同数据的都需要同步机制。这样一来原来对同步机制没有需求变成需要在整个程序的范围内需要同步机制。
When a variable is guarded by a lock meaning that every access to that variable is performed with that lock held you've ensured that only one thread at a time can access that variable. When a class has invariants that involve more than one state variable, there is an additional requirement: each variable participating in the invariant must be guarded by the same lock. This allows you to access or update them in a single atomic operation, preserving the invariant. SynchronizedFactorizer demonstrates this rule: both the cached number and the cached factors are guarded by the servlet object's intrinsic lock.
当一个变量被一个锁守护的时候,这意味着对变量的每次访问都拥有了你指定的锁。当一个类的一致性涉及到超过一个状态变量时,这就有了一个附加需求。每一个参与到一致性中的变量都必须被同一把锁守护。这就要求你在同一个原子操作中访问或者修改它们。SynchronizedFactorizer展示了这样的规则:被缓存的数值和被缓存的因数都被servlet的内在锁对象守护。
For every invariant that involves more than one variable, all the variables involved in that invariant must be guarded by the same lock.
对于涉及到多个变量的不变性,所有变量都必须被同一把锁守护。

If synchronization is the cure for race conditions, why not just declare every method synchronized? It turns out that such indiscriminate application of synchronized might be either too much or too little synchronization. Merely synchronizing every method, as Vector does, is not enough to render compound actions on a Vector atomic:
if (!vector.contains(element))
vector.add(element);
如果同步机制是用来解决条件竞争,那么为什么不把所有方法都变成同步的呢?事实证明这种不分好坏的同步机制没有半点作用,要么同步机制太多,要么太少。仅仅把每一个方法都同步化(像Vector那样做)并不能保证将复合操作都放在一个Vector原子操作中。
This attempt at a put-if-absent operation has a race condition, even though both contains and add are atomic. While synchronized methods can make individual operations atomic, additional locking is required when multiple operations are combined into a compound action. (See Section 4.4 for some techniques for safely adding additional atomic operations to thread-safe objects.) At the same time, synchronizing every method can lead to liveness or performance problems, as we saw in SynchronizedFactorizer.
尽管保存和增加都是原子的,这种put-if-absent的操作还是存在条件竞争。尽管同步方法使得单独的操作是原子的,当多个操作合并成一个复合操作的时候,附加的锁还是需要的(第4.4节中,展示了向线程安全的对象中增加原子操作的方法)。同时,把每一个方法都同步化会导致存活性和性能问题,就像我们在SynchronizedFactorizer类中看到的那样。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值