Java Thread Programming 1.7 – Concurrent Access to Objects and Variables
來源:互聯網
上載者:User
When multiple threads are interacting with an object, controls need to be in place to ensure that the threads don’t adversely affect one another. This chapter deals with issues that can introduce subtle errors in your application. An application that fails to safely control concurrent access can work properly most of the time—maybe nearly all the time—but will occasionally produce erroneous results. This makes the understanding and disciplined use of the information in this chapter critical to writing truly thread-safe applications that work properly all the time. 如果你的系統出現了莫名其妙的問題,很有可能是並發訪問的問題哦
volatile Member Variable ModifierThe
Java Language Specification indicates that for optimal speed, individual threads are permitted to keep a working copy of shared member variables and only reconcile them with the shared original occasionally. To be more accurate, the word “occasionally” in the last sentence should be replaced with “when a thread enters or leaves a synchronized block of code.” I’ll tell you more about synchronized blocks later in this chapter. When only one thread is interacting with the member variables of an object, this optimization works very well and can allow for faster execution. When two (or more) threads are simultaneously working with an object, care must be taken to ensure that changes made to a shared member variable by one thread are seen by the other. Java規範中說明,為了最佳化速度,每個線程都擁有共用成員變數的一個拷貝,當線程開始或結束一個同步代碼塊時,才回寫變數值。當單線程時,這種機制很好的工作,可以獲得更高的效能,但是多個線程協同工作時,必須考慮,被一個線程改變的公開變數要被其他線程知道。 The volatile keyword is used as a modifier on member variables to force individual threads to reread the variable’s value from shared memory every time the variable is accessed. In addition, individual threads are forced to write changes back to shared memory as soon as they occur. This way, two different threads always see the same value for a member variable at any particular time. Chances are that most of you expected this behavior from the Java VM already. In fact, many experienced Java developers don’t understand when the use of volatile is necessary. Volatile關鍵字,如果用來修飾成員變數,則強迫每個線程每次訪問這個變數都要重新讀取,每次改變變數值時都要儘快回寫。這樣,兩個不同的線程在特定時間總能獲得相同的變數值 Use volatile on member variables that can be accessed by two or more threads unless all the threads access the variables within synchronized blocks of code. If a member variable remains constant after construction (it is read-only), there is no need for it to be volatile. 如果一個成員變數是唯讀,則沒有必要設定為volatile如果一個所有的線程訪問這個變數都是在一個同步代碼塊中,則沒有必要設定為volatile The volatile modifier exists to request that the VM always access the shared copy of the variable. This is less efficient than allowing the VM to perform optimizations by keeping a private copy. You should use volatile only when it is necessary; overuse will unnecessarily slow the application’s execution. Volatile要求虛擬機器的每次對變數的請求都要回訪,這對於每個線程保持一個私人拷貝是低效的,會降低訪問速度,如非必要,不要使用
synchronized Method ModifierThe addition of the synchronized modifier to a method declaration ensures that only one thread is allowed inside the method at a time. This can be useful in keeping out other threads while the state of an object is temporarily inconsistent. Synchronized修飾,用來修飾一個方法申明,可以保證同一時間只有一個線程能夠調用此方法。這種機制能夠防止對象處於不連續狀態時被其他線程訪問
Two Threads Simultaneously in the Same Method of One ObjectIf two or more threads are simultaneously inside a method, each thread has its own copy of local variables. 兩個或多個線程同時調用同一對象的相同方法,則每一個線程擁有局部變數的拷貝
One Thread at a Time
More than one thread can be inside a method, and each thread keeps a copy of its own local variables. However, there are times when application constraints require that only one thread be permitted inside a method at a time.
When a thread encounters a synchronized instance method, it blocks until it can get exclusive access to the object-level mutex lock.
Mutex is short for mutual exclusion. A mutex lock can be held by only one thread at a time. Other threads waiting for the lock will block until it is released. When the lock is released, all the threads waiting for it compete for exclusive access. Only one will be successful, and the other threads will go back into a blocked state waiting for the lock to be released again. 保證同一時間只有一個線程調用某個方法,只需在方法申明前加上synchronized。當一個線程碰到申明為sychronized的方法執行個體時,會阻塞,直到它獲得對象級互斥鎖訊號量的訪問權,互斥訊號在某個時刻只能被一個線程擁有,其他等待此訊號的線程必須等候此訊號被釋放,此互斥鎖釋放後,其他線程競爭訪問權,成功的線程運行,其他的線程繼續阻塞等待此互斥鎖的再次釋放。
Two Threads, Two ObjectsEvery instance of a class has its
own object-level lock. Although the doStuff() method is synchronized, there is no competition for exclusive access to the object-level lock. Each instance, obj1 and obj2, has its own object-level lock. When threadA enters the doStuff() method of obj1 (line 1), it acquires exclusive access to the object-level lock for obj1. When threadB enters the doStuff() method of obj2 (line 3), it acquires exclusive access to the object-level lock for obj2. 每個對象的執行個體有自己的對象級鎖。換言之,synchronized鎖是建立在對象的執行個體上的,而不是抽象的對象上的。多個線程調用同一個對象執行個體的同一個synchronized方法時,會同步訪問,而多個線程調用同一對象不同執行個體的同一個synchronized方法時,是不互相互斥訪問的。
Avoiding Accidental Corruption of an ObjectIt’s an unavoidable fact that the object must be in an inconsistent state for a brief period of time, even with everything except the assignments taken out: public
synchronized void setNames(String firstName, String lastName) {
fname = firstName;
lname = lastName;} No matter how fast the processor is, it’s possible that the thread scheduler could swap out the thread making the changes after it has changed fname but before it has changed lname. Holding an object-level lock does not prevent a thread from being swapped out. And if it is swapped out, it continues to hold the object-level lock. Because of this, care must be taken to ensure that all reads are blocked when the data is in an inconsistent state. CleanRead (see Listing 7.16) simply adds the synchronized method modifier to getNames() to control concurrent reading and writing. 兩個線程同時調用某執行個體的方法修改某些變數值,會導致這些變數值在某個時刻狀態不連續,加上synchronized修飾此方法,可保證此變數值的完整性
Deferring Access to an Object While It Is Inconsistent一個線程在調用某個synchornized設定值的方法修改某個執行個體的某些變數值的時候,只是限制其他線程同時調用此方法,並不限制其他線程調用別的方法訪問這些變數值,如果此時另一個線程在調用某個獲得值的方法訪問這些變數,某些變數可能已經被修改,而另外一些變數還沒有修改,有可能造成變數的不連續。將獲得值得方法前加上synchronized修飾符,可解決此問題。因為,一個對象執行個體只有一個對象級鎖,當一個synchronized方法被調用時,此執行個體的其他synchronized方法必須等待此鎖釋放。
If two or more threads might be simultaneously interacting with the member variables of an object, and at least one of those threads might change the values, it is generally a good idea to use synchronized to control concurrent access. If only one thread will be accessing an object, using synchronized is unnecessary and slows execution.
synchronized Statement BlockThe synchronized block can be used when a whole method does not need to be synchronized or when you want the thread to get an object-level lock on a different object. Synchronized block適用範圍1、 不是整個方法都需要同步2、 需要鎖定不同的對象 The synchronized statement block looks like this:
synchronized ( obj ) {// block of code} where obj is a reference to the object whose object-level lock must be acquired before entering the block of code.This setPoint() methodpublic
synchronized void setPoint(int x, int y) {this.x = x;this.y = y;}can be rewritten to instead use a synchronized block:public void setPoint(int x, int y) {
synchronized ( this ) { this.x = x; this.y = y;}} The behavior of both versions of setPoint() is virtually the same. They do compile to different byte-code, but both of them make sure that they have exclusive access to the object-level lock for the instance before making changes to x and y.
Reducing the Time That the Lock Is HeldA synchronized block can be used to reduce the time that the object-level lock is held. If a method does a lot of other things that don’t require access to the member variables, it can shorten the time that it holds the lock to just the critical portion:public void setValues(int x, double ratio) {// Some other, long-running statements that don’t work// with the member variables go here.// ...double processedValA = // ... long calculation ...double processedValB = // ... long calculation ...// ...
synchronized ( this ) { a = processedValA; b = processedValB;}In setValues(), exclusive access to the object-level lock is not needed until the time-consuming calculations have been made and the results are ready to be stored. At the bottom of the method, the object-level lock is acquired and held briefly to simply assign new values to the member variables a and b. 減少鎖定時間,這種方式只對需要同步的地方加鎖,最小化鎖定開銷
Locking an Object Other Than this鎖定一個非this的對象 the reference mutex indicates the object whose object-level lock must be acquired before entering the statement block. It can be a reference to any object in the VM, not just this. Regardless of how a thread leaves a synchronized block, it automatically releases the lock. This includes a return statement, a throw statement, or just falling through to the next statement after the block. Calling a method from within the synchronized block does not constitute
leaving the block (the lock is still held). 可以同步虛擬機器內的任何對象,而不僅僅是this。 Sometimes you will need to call two synchronized methods on an object and be sure that no other thread sneaks in between the calls. Consider this code fragment from a class called Bucket: 有時候兩個synchronized方法必須同時執行 public class Bucket extends Object {// ...public
synchronized boolean isSpaceAvailable() { // ...public
synchronized void add(BucketItem o) throws NoSpaceAvailableException { // ...public
synchronized BucketItem remove() { // ...// ...} 一種調用方法:Bucket b = // ...// ...if ( b.isSpaceAvailable() ) {b.add(item);} This is fine if only one thread is interacting with this instance of Bucket. But if multiple threads are potentially trying to add BucketItem objects to the same Bucket, a new approach has to be taken to avoid a race condition. Imagine that threadA checks and sees that space is available, but before it actually adds its item, threadB checks and also sees that space is available. Now threadA and threadB are racing to actually add an item. Only one can win the race, and that thread gets to add its item. The other thread will fail to add its item and will throw a NoSpaceAvailableException. To prevent this problem, a synchronized block should be wrapped around the two method calls: 這種方法只有一個線程時是正確的,否則會發生同步錯誤 Bucket b = // ...// ...
Synchronized(b){if ( b.isSpaceAvailable() ) {b.add(item);}} The synchronized block uses the object-level lock on b, the Bucket instance. This is the same lock that must be acquired before entering the isSpaceAvailable() and add() methods. If a thread can get the object-level lock and enter the synchronized block, it is guaranteed to be able to invoke isSpaceAvailable() and add() without blocking. Because it already has the object-level lock for b, there is no delay or competition to enter the synchronized methods. In addition, no other thread can invoke these methods until the first thread leaves the synchronized block. 把這段代碼放在一個b鎖中,則其他線程不能調用b中的任何synchronized方法,一旦一個某個線程獲得b鎖,則必然可以執行鎖中的代碼,沒有其他線程的競爭
static synchronized MethodsIn addition to the
object-level lock that exists for each instance of a class, there is a
class-level lock that all instances of a particular class share. Every class loaded by the VM has exactly one
class-level lock. If a method is both static and synchronized, a thread must get exclusive access to the class-level lock before entering the method. The class-level lock can be used to control concurrent access to static member variables. Just as the object-level lock was needed to prevent data corruption in non-static member variables, the class-level lock is needed to prevent corruption of static member variables. Even when no variables are involved, the synchronized modifier can be used on static methods simply to ensure that only one thread is inside the method at a time.
object-level lock 對應於類的每個執行個體
class-level lock 對應於類所有執行個體,虛擬機器產生的每一個類都有一個
class-level lock,對於一個static synchronized method,在調用此方法前,線程會排他訪問
class-level lock。
Using the Class-Level Lock in a synchronized StatementThe synchronized statement can also use a class-level lock. This can be useful if a static method runs for a long period of time. Additionally, it can be used to ensure that two static method calls by one thread are not interleaved with a call by another thread. To lock on the class-level lock, use the following codesynchronized ( ClassName.
class ) {// body}
使用Class-Level Lock來同步代碼塊,使用synchronized(類名.class){}
Synchronization and the Collections APIVector and Hashtable were originally designed to be multithread-safe. Take Vector, for example—the methods used to add and remove elements are synchronized. If only one thread will ever interact with an instance of Vector, the work required to acquire and release the object-level lock is wasted. Vector和Hashtable最初設計成安全執行緒,他們中的大部分方法都是synchronized,這在單線程程式中是不必要的 The designers of the Collections API wanted to avoid the overhead of synchronization when it wasn’t necessary. As a result, none of the methods that alter the contents of a collection are synchronized. If a Collection or Map will be accessed by multiple threads, it should be wrapped by a class that synchronizes all the methods. Collections are not inherently multithread-safe. Extra steps must be taken when more than one thread will be interacting with a collection to make it multithread-safe. Collection API設計為當需要的時候為synchronized,預設不是安全執行緒的,如果在多線程環境中使用Collection,首先要把其轉換為安全執行緒 There are several static methods in the Collections class that are used to wrap unsynchronized collections with synchronized methods:public static Collection
synchronizedCollection(Collection c)public static List
synchronizedList(List l)public static Map
synchronizedMap(Map m)public static Set
synchronizedSet(Set s)public static SortedMap
synchronizedSortedMap(SortedMap sm)public static SortedSet
synchronizedSortedSet(SortedSet ss) 集合類中提供了幾種靜態方法用來將非安全執行緒集合轉換為安全執行緒集合,如上,用法如下: Basically, these methods return new classes that have synchronized versions of the collections’ methods. To create a List that is multithread-safe and backed by an ArrayList, use the following:List list = Collections.synchronizedList(new ArrayList()); When synchronizing collections, do not keep any direct reference to the original unsynchronized collection. This will ensure that no other thread accidentally makes uncoordinated changes. 為了保證集合約步,不要直接使用最初非安全執行緒的集合。
Safely Copying the Contents of a List into an Array三種方法安全拷貝List為數組
Safely Iterating Through the Elements of a Collection安全遍曆集合,遍曆的時候防止其他線程添加或修改集合中資料The elements of a Collection can be stepped through one by one by using an Iterator. In a multithreaded environment, you will generally want to block other threads from adding or removing elements while you are iterating through the current collection of elements.
DeadlocksUsing locks to control concurrent access to data is critical to avoid subtle race conditions within applications. However, trouble can arise when a thread needs to hold more than one lock at a time. 使用鎖機制控制關鍵資料的並發訪問,當一個線程同時擁有兩個以上鎖時可能會出現問題:死結 Deadlocks can be extremely difficult to track down. Generally, most of an application will continue to run, but a couple of threads will be stuck in a deadlock. To make matters worse, deadlocks can hide in code for quite a while, waiting for a rare condition to occur. An application can run fine 99 out of 100 times and only deadlock when the thread scheduler happens to run the threads in a slightly different order. Deadlock avoidance is a difficult task.
Most code is not vulnerable to deadlocks, but for the code that is, try following these guidelines to help avoid deadlocks:l Hold locks for only the minimal amount of time necessary. Consider using synchronized statement blocks instead of synchronizing the whole method.l Try to write code that does not need to hold more than one lock at a time. If this is unavoidable, try to make sure that threads hold the second lock only for a brief period of time.l Create and use one
big lock instead of several small ones. Use this lock for mutual exclusion instead of the object-level locks of the individual objects.l Check out the InterruptibleSyncBlock class in Chapter 17. It uses another object to control concurrent access to a section of code. Additionally, instead of having a thread block on the synchronized statement, the thread is put into a wait-state that
is interruptible. I’ll tell you more about the wait-notify mechanism in Chapter 8.
死結很難避免,一般有以下原則可降低死結出現的可能性l 鎖定盡量短的時間,盡量使用synchronized statement而不是使用synchronized整個方法l 盡量不同時擁有超過一個鎖,如果不可避免,要保證線程持有第二個鎖盡量短的時間l 建立使用一個大鎖,而不是多個小鎖,用一個新的大鎖代筆各個對象上的小鎖l 17章給出InterruptibleSyncBlock類,它使用另一個對象來控制碼塊的並發訪問。不要讓一個線程在同步一個statement時阻塞,而是置於可中斷的wait狀態,使用下一章將討論的wait-notify機制。
Speeding Concurrent AccessTo speed up execution, do not use synchronized unnecessarily. Be sure that it’s really needed for proper functioning. If synchronization is necessary, see if using a synchronized statement block would work instead of a synchronized method. Although this won’t decrease the cost of acquiring and releasing the lock, it will reduce contention for the lock among the other threads because the lock is held for a shorter period of time. 加快同步訪問速度的方法:1、 如非必要,不適用synchronized2、 能使用synchronized statement block就不使用synchronized method,會降低衝突的機率
SummaryIn this chapter, I showed you: l How to use
volatile to force unsynchronized threads to work with the shared copy of a variable instead of a private working copy.l How to use the
synchronized method modifier on non-static methods to require a thread to get exclusive access to the object-level lock before entering the method.l How to use the
synchronized statement block to require a thread to get exclusive access to the object-level lock of the specified object before executing the code within the block.l How to use the
synchronizedmethod modifier on
static methods to require a thread to get exclusive access to the class-level lock before entering the method.l How to work
safely with the Collections API in a multithreaded environment.l How to understand the causes of
deadlocks, and how to try to avoid them. 使用volatile修飾符保證多線程變數的同步使用synchronized修飾符保證一個對象執行個體在object-level lock上同步使用synchronized修飾符保證對象static method 在class-level lock上同步為保證安全執行緒使用集合類,首先要對集合類進行轉換產生安全執行緒的集合類