Basic knowledge "Five"---common pitfalls of Java multi-threading

Source: Internet
Author: User

1. Start the thread in the constructor

I see this in a lot of code, starting a thread in the constructor, like this:

1  Public classa{2     PublicA () {3        This. x=1; 4        This. y=2; 5        This. thread=NewMyThread (); 6        This. Thread.Start (); 7    }  8      9} 

What problem does this cause? If a Class B inherits from Class A, the constructor of a must be called before the constructor call of B, according to the order in which the Java class is initialized, and the thread thread will start before B is fully initialized, and when thread runs, it uses some of the variables in Class A. Then you may not be using the values you expect, because in the constructor of B you may assign new values to these variables. This means that there will be two threads using these variables, but these variables are not synchronized.

There are two ways to solve this problem: To set A to final, not to inherit, or to provide a separate start method to start a thread, rather than in a constructor.

2. Incomplete synchronization

All know that the effective way to synchronize a variable is to protect it with synchronized, synchronized may be an object lock, or a class lock, see if you are a class method or an instance method. However, when you synchronize a variable in the A method, you also need to synchronize elsewhere where the variable appears, unless you allow weak visibility or even generate an error value. Code similar to this:

1 classa{2   intx; 3    Public intGetX () {4      returnx; 5   }  6    Public synchronized voidSetX (intx)7   {  8       This. x=x; 9   }  Ten}

The setter method of X is synchronous, but the getter method is not, so there is no guarantee that the X that other threads get through Getx is the most recent value. In fact, the synchronization of the setx here is not necessary because the write to int is atomic, and the JVM specification has ensured that multiple synchronizations have no meaning; Of course, if this is not int, but double or long, then both GETX and SETX will need to be synchronized. Since both the double and long are 64 bits, both the write and read are divided into two 32 bits (depending on the implementation of the JVM, some JVM implementations may guarantee that read, write is atomic for long and double), and there is no guarantee of atomicity. Code like the above can actually be resolved by declaring variables as volatile.


3, when using an object when the lock, changed the reference to the object, resulting in synchronization failure.

This is also a common error, similar to the following code:

1 synchronized (array[0])   2 {  3   ...   4    array[0]=New  A ();   5    ......   6 }   

The synchronization block uses array[0] as the lock, but the reference that array[0] points to is changed in the synchronization block. Analyze this scenario, the first thread acquires the lock of Array[0], the second thread waits for the array[0] to be retrieved, and after changing the reference of Array[0], the third thread acquires a new array[0] lock, and the first and 32 threads hold a different lock. The purpose of synchronizing mutual exclusion is completely out of reach. This modification of the code is usually done by declaring the lock as a final variable or introducing a business-independent lock object, which guarantees that the reference will not be modified within the synchronization block.

4. Wait () is not called in the loop.

Wait and notify are used to implement condition variables, and you may know that you need to call wait and notify in the synchronization block, in order to ensure that the conditions change to be atomic and visible. Often see a lot of code do synchronization, but not in the loop call wait, but use if even no condition to judge:

1 synchronized (lock)   2 {  3    if(IsEmpty ()  4     lock.wait ();   5      6 }  

The condition is judged by the use of if, what is the problem? Notify or notifyall may be called before the condition is judged, so the condition is satisfied and not waiting, which is fine. When the condition is not met, the wait () method is called, releasing the lock lock and entering the standby hibernation state. If the thread is under normal conditions, that is, after the condition has been changed, then there is no problem, and the condition satisfies the following logical operation. The problem is that the thread can be accidentally or even maliciously awakened, and the thread performs the subsequent operation without the conditional judgment being met again. An unexpected wake-up scenario might be a call to Notifyall, possibly a malicious wake-up, or a rare automatic awakening (called "pseudo-wakeup"). Therefore, in order to prevent this condition is not satisfied in the implementation of the following operations, need to be awakened after the condition, if the condition is not satisfied, continue to enter the waiting state, the condition is satisfied, before the subsequent operation.

1 synchronized (lock)   2 {  3    while(IsEmpty ()  4     lock.wait ();   5      6

It is more serious to call wait without a conditional judgment, because it is possible that notify has been called before waiting, and then after waiting for a wait to hibernate, there is no guarantee that the thread will wake up.

5, the range of synchronization is too small or too large.

Synchronization is too small and may not be synchronized at all, and the range of synchronization is too large and can affect performance. A common example of a too small synchronization range is the mistaken belief that two synchronous methods are called together and are synchronized, and it is atomic+atomic!=atomic to remember.

1 Map map=collections.synchronizedmap (new  HashMap ());   2 if (!map.containskey ("a")) {  3          map.put ("A", value);   4

This is a typical error, the map is thread-safe, and the ContainsKey and put methods are thread-safe, but two thread-safe methods are not necessarily thread-safe to be called together. Because between ContainsKey and put, there may be other threads that have been first put in a, it may overwrite the values set by other threads, resulting in a loss of value. The solution to this problem is to expand the synchronization scope because object locks are reentrant, so it is no problem to synchronize the same lock object on top of the thread security method.

1 Map map = Collections.synchronizedmap (new  HashMap ());   2 synchronized (map) {  3      if (!map.containskey ("a")) {  4          Map.put ("A", value);   5      }  6  }   

Note that increasing the range of locks, but also to ensure the use of the same lock, or it is likely to cause a deadlock. The lock used by Collections.synchronizedmap (new HashMap ()) is the map itself, so there is no problem. Of course, the above situation is now more recommended to use CONCURRENTHASHMAP, which has the Putifabsent method to achieve the same purpose and to satisfy thread safety.

There are also many examples of large synchronization ranges, such as the new large object in the synchronization block, or the time-consuming IO operation (Operation database, WebService, etc.). When you have to call a time-consuming operation, be sure to specify a time-out, such as setting up connect timeout and read timeout when you invoke a URL through URLConnection, to prevent the lock from being released exclusively. In situations where the synchronization scope is too large, the actions that do not have to be synchronized are moved out of the synchronization block to ensure thread safety.

6. Correct use of volatile

After Jdk5 modified the semantics of volatile, volatile as a lightweight synchronization strategy has been a lot of use. The strict definition of volatile refers to the JVM spec, which is only a discussion of what volatile can do and what it cannot do.

What can volatile be used to do?

1) Status indicator, analog control mechanism. Common uses such as controlling whether a thread is stopped:

1 Private volatile Booleanstopped; 2  Public voidClose () {3Stopped=true; 4 }  5  6  Public voidrun () {7  8     while(!stopped) {  9       //Do somethingTen    }   One       A}

The premise is that there are no blocking calls in the do something. Volatile guarantees the visibility of the stopped variable, and reading the stopped variable in the Run method is always the most recent value in main memory.

2) security release, such as fix DLC problem.

1 Private volatileIobufferallocator instance; 2  Publiciobufferallocator Getinsntace () {3     if(instance==NULL){  4         synchronized(Iobufferallocator.class) {  5             if(instance==NULL)  6Instance=NewIobufferallocator (); 7         }  8     }  9     returninstance; Ten}

3) Low-cost read-write lock

1  Public classCheesycounter {2     Private volatile intvalue; 3  4      Public intGetValue () {returnvalue;} 5  6      Public synchronized intincrement () {7         returnvalue++; 8     }  9}

Synchronized guarantees updated atomicity, and volatile guarantees visibility between threads.

What does volatile not do?

1) cannot be used to do counter

1  Public classCheesycounter {2     Private volatile intvalue; 3  4      Public intGetValue () {returnvalue;} 5  6      Public intincrement () {7         returnvalue++; 8     }  9}

Because value++ is actually composed of three operations: read, modify, write, volatile does not guarantee that the sequence is atomic. The modification of value depends on the most recent value. The solution to this problem is to synchronize the increment method, or use the Atomicinteger atomic class.

2) invariant form with other variables

A typical example is the definition of a data range, which requires a guaranteed constraint lower< upper.

1  Public classNumberrange {2     Private volatile intLower, Upper; 3  4      Public intGetlower () {returnLower;} 5      Public intGetupper () {returnUpper;} 6  7      Public voidSetlower (intvalue) {   8         if(Value >Upper)9             Throw Newillegalargumentexception (); TenLower =value;  One     }   A   -      Public voidSetupper (intvalue) {    -         if(Value <Lower) the             Throw Newillegalargumentexception ();  -Upper =value;  -     }   -}

Although lower and upper are declared volatile, setlower and setupper are not thread-safe methods. Assuming that the initial state is (0,5), calls Setlower (4) and Setupper (3), two threads intersect, and the final result may be (4,3), violating the constraint. The solution to this problem is to synchronize Setlower and Setupper:

1  Public classNumberrange {2     Private volatile intLower, Upper; 3  4      Public intGetlower () {returnLower;} 5      Public intGetupper () {returnUpper;} 6  7      Public synchronized voidSetlower (intvalue) {   8         if(Value >Upper)9             Throw Newillegalargumentexception (); TenLower =value;  One     }   A   -      Public synchronized voidSetupper (intvalue) {    -         if(Value <Lower) the             Throw Newillegalargumentexception ();  -Upper =value;  -     }   -}

Basic knowledge "Five"---common pitfalls of Java multi-threading

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.