Top
首页 > 老文章 > 正文

Java Thread Programming 1.7 - Concurrent Access to Objects and Variables

Java Thread Programming 1.7 - Concurrent Access to Objects and Variables

Java Thread Programming 1.7 - Concurrent Access to Objects and Variables
发布时间:2006-03-08 23:53        来源:        作者:
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 Modifier
 
The 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 Modifier
 
The 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 Object
If 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 Objects
Every 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 Object
It’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 Block
The 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() method
public 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 Held
A 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 Methods
 
In 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 Statement
 
The 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 code
synchronized ( ClassName.class ) {
// body
}
 
使用Class-Level Lock来同步代码块,使用synchronized(类名.class){}
 
Synchronization and the Collections API
 
Vector 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.
 
Deadlocks
 
Using 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 Access
 
To 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、  如非必要,不适用synchronized
2、  能使用synchronized statement block就不使用synchronized method,会降低冲突的概率
 
Summary
 
In 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 synchronized method 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上同步
为保证线程安全使用集合类,首先要对集合类进行转换生成线程安全的集合类
加载更多

专题访谈

合作站点
stat