synchronized : 规则, 推论与实践

14.3.Synchronization.


Rule 1. synchronized:只影响多线程,不影响本线程 (Locks are owned per thread, so invoking a synchronized method from within another method synchronized on the same object will proceed without blocking, releasing the lock only when the outermost synchronized method returns.)

Rule 2. synchronized:只影响synchronized code,不影响非synchronized code. (Unsynchronized access does not wait for any locks but proceeds regardless of locks that may be held on the object.)

This is yet another reason to prefer accessor methods to public or protected fields: Using methods, you can synchronize access to the data, but you have no way to do so if the fields can be accessed directly outside your class.

Rule 3. synchronized: 只影响该类, 不影响子类或不受父类影响 (Synchronization requirements are a part of the implementation of a class.)


For example, a class that uses a private field as a lock object prevents an extended class from using the same synchronization mechanism. the extended class would have to define its own lock object (perhaps this) and override every method of the superclass to use this new synchronization mechanism.

Rule 4. synchronized: 类对象和实例对象互不影响(Acquiring the Class object lock in a static synchronized method has no effect on any objects of that class.) 

Rule 5. synchronized: 外围类实例和内嵌类实例互不影响(Another common use of the synchronized statement is for an inner object to synchronize on its enclosing object)

Like any other object, an inner object is independently synchronized. acquiring the lock of an inner object has no effect on its enclosing object's lock, nor does acquiring the lock of an enclosing object affect any enclosed inner objects. An inner class that needs to synchronize with its enclosing object must do so explicitly and a synchronized statement is a perfect toolthe alternative is to declare a synchronized method in the enclosing class just for the inner class to use.

Utility Method 1.

You can ask whether the current thread holds the lock on a given object by passing that object to the Thread class's static holdsLock method, which returns true if the current thread does hold the lock on that object. This is typically used to assert that a lock is held when needed. For example, a private method that expects to be invoked only from synchronized public methods might assert that fact:

assert Thread.holdsLock(this);

Conclusion 1. Don't Use synchronized(obj.getClass())

If you need a synchronized statement to use the same lock used by static synchronized methods, you can use the class literal for your class (see example below).It would also be wrong to use the Object method getClass to retrieve the Class object for the current instance: In an extended class, such as AttributedBody, that would return the Class object for AttributedBody not Body, and so again, different locks would be used and interference would not be prevented.

Conclusion 2. Client-side synchronization

An object can have its lock acquired, which prevents any of its synchronized methods from being invoked except by the lock holder performing the series of invocations. Similarly, you can acquire the locks of each of the objects involved and then invoke the series of methods on those objects but watch out for deadlock (see Section 14.7 on page 362). As long as the object's methods are already synchronized on the current object's lock, then other clients of the object need not use client-side synchronization.



14.4. wait, notifyAll, and notify

The wait and notification methods are defined in class Object and are inherited by all classes. They apply to particular objects, just as locks do.

There is a standard pattern that is important to use with wait and notification. The thread waiting for a condition should always do something like this:

synchronized void doWhenCondition() {
 while (!condition)
 wait();
… Do what must be done when the condition is true …
}

A number of things are going on here:

  • Everything is executed within synchronized code. If it were not, the state of the object would not be stable. For example, if the method were not declared synchronized, then after the while statement, there would be no guarantee that the condition remained TRue: Another thread might have changed the situation that the condition tests.

  • One of the important aspects of the definition of wait is that when it pauses the thread, it atomically releases the lock on the object. Saying that the thread suspension and lock release are atomic means that they happen together, indivisibly. Otherwise, there would be a race hazard: A notification could happen after the lock is released but before the thread is suspended. The notification would have no effect on the thread, effectively getting lost. When a thread is restarted after being notified, the lock is atomically reacquired.

  • The condition test should always be in a loop. Never assume that being awakened means that the condition has been satisfiedit may have changed again since being satisfied. In other words, don't change the while to an if.

Using notifyAll wakes up all waiting threads, whereas notify picks only one thread to wake up.

Multiple threads may be waiting on the same object, possibly for different conditions. If they are waiting for different conditions, you should always use notifyAll to wake up all waiting threads instead of using notify. Otherwise, you may wake up a thread that is waiting for a different condition from the one you satisfied. That thread will discover that its condition has not been satisfied and go back to waiting, while some thread waiting on the condition you did satisfy will never get awakened. Using notify is an optimization that can be applied only when:

  • All threads are waiting for the same condition

  • At most one thread can benefit from the condition being met

  • This is contractually true for all possible subclasses

Otherwise you must use notifyAll. If a subclass violates either of the first two conditions, code in the superclass that uses notify may well be broken. To that end it is important that waiting and notification strategies, which include identifying the reference used (this or some other field), are documented for use by extended classes.

In a multithreaded system you very rarely want to busy-wait. You should always suspend until told that what you are waiting for may have happened. This is the essence of thread communication with the wait and notifyAll/notify mechanism.


14.5. Details of Waiting and Notification

public final void wait(long timeout) tHRows InterruptedException

The current thread waits until one of four things happens: notify is invoked on this object and this thread is selected to be runnable; notifyAll is invoked on this object; the specified timeout expires; or the thread has its interrupt method invoked. timeout is in milliseconds. If timeout is zero, the wait will not time out but will wait indefinitely for notification. During the wait the lock of the object is released and is automatically reacquired before wait completesregardless of how or why wait completes. An InterruptedException is thrown if the wait completes because the thread is interrupted.

You can invoke these methods only from within synchronized code, using the lock for the object on which they are invoked. The invocation can be directly made from the synchronized code, or can be made indirectly from a method invoked in such code. You will get an IllegalMonitorStateException if you attempt to invoke these methods on an object when you don't hold its lock.

细节 1. Only notifications that occur after the wait commences will affect a waiting thread.

If no threads are waiting when either notifyAll or notify is invoked, the notification is not remembered. If a thread subsequently decides to wait, an earlier notification will have no effect on it.

细节 2. wait(long timeout)未必会导致 wait 在有限时间内返回

The use of a time-out is a defensive programming measure that allows you to recover when some condition should have been met but for some reason (probably a failure in another thread) has not. Because the lock of the object must be reacquired, the use of a time-out cannot guarantee that wait will return in a finite amount of time.

细节 3.wait始 终在循环中的另一个原因

It is also possible that some virtual machine implementations will allow so-called "spurious wakeups" to occurwhen a thread returns from wait without being the recipient of a notification, interruption, or time-out. This is another reason that wait should always be performed in a loop that tests the condition being waited on.



14.7. Deadlocks

One common technique is to use resource ordering. With resource ordering you assign an order on all objects whose locks must be acquired and make sure that you always acquire locks in that order. This makes it impossible for two threads to hold one lock each and be trying to acquire the lock held by the otherthey must both request the locks in the same order, and so once one thread has the first lock, the second thread will block trying to acquire that lock, and then the first thread can safely acquire the second lock.

14.10. The Memory Model: Synchronization and volatile

Rule 1. 除long和double外, 变量的读写都是原子操作; 然而这对于 get / modify / set 操作序列 (像 a++, b--) 毫无帮助, 它们总是需要被同步

The language guarantees that reading or writing any variables, other than those of type long or double, is atomicthe variable will only ever hold a value that was written by some thread, never a partial value intermixing two different writes. This means, for example, that an atomic variable that is only written by one thread and read by many threads need not have access to it synchronized to prevent corruption because there is no possibility of interference. This does not help with getmodifyset sequences (such as ++), which always require synchronization

Rule 2. 原子存取并不意味着一个线程读出来的变量永远是最新的; 事实上, 如果没有同步, 一个线程可能永远都看不见另外一个线程对变量的更新

The rules that determine how memory accesses are ordered and when they are guaranteed to be visible are known as the memory model of the Java programming language. If all reads and writes to a variable occur only when a specific monitor is held, then each read of the variable is guaranteed by the memory model to return the value that was most recently written to it.

Rule 3. 作为第二种同步机制, 使用 volatile 声明的变量能够保证一个线程读出来的变量永远是最新的

There is a second synchronization mechanism that doesn't provide the exclusive access of monitors, but that again ensures that each read of a variable returns the most recently written valuethe use of volatile variables. Fields (but not array elements) can be declared with the volatile modifier. A write to a volatile variable synchronizes with all subsequent reads of that variable. If currentValue was declared as volatile then the example code we showed would be correctly synchronized and the latest value would always be displayed. The use of volatile variables is seldom a replacement for the use of synchronized methods or statements on its own, because they don't provide atomicity across different actions. Rather, volatile variables are most often used for simple flags to indicate something has occurred, or for writing lock-free algorithms that incorporate use of the atomic variables mentioned in Section 25.9.

Rule 4. volatile 另外一个副作用就是让long或double类型的变量读写也变成原子操作


几个最佳实践: A few other synchronization actions help make multithreading work nicely:

  • Starting a thread synchronizes with the first action performed by that thread when it executes. This ensures that a newly started thread sees any data that was initialized by the creating threadincluding the thread's own fields.

  • The final action of a thread synchronizes with any action that detects that the thread has terminatedsuch as calling isAlive or invoking join on that thread. This ensures, for example, that if you join a thread you can see all data written by that thread before it terminatedsuch as the results of its computation.

  • Interrupting a thread synchronizes with any other action that determines that the thread has been interrupted, such as the thread throwing InterruptedException or another thread invoking isInterrupted on the thread.

  • The write of the default value (zero, null, or false) to any field synchronizes with the first action in any thread. This ensures that even in incorrectly synchronized programs a thread will never see arbitrary values in fieldseither a specific value written by some thread will be seen or the default value of the field will be seen.

你可能感兴趣的:(synchronized)