Java thread security and DCL

Source: Internet
Author: User
Tags bbcode

I have written an article about DCL before, and recently I have received another question. I wanted to reply directly, but I don't want to read the original article. The sequence analysis is actually very difficult. I will not analyze the sequence directly this time, but start with the basic concepts. I hope you can see it easily.

If you search for the reasons why DCL fails in Java on the Internet, you will talk about how the compiler optimizes the cloud. I believe everyone will feel frustrated and helpless, I am not confident in my own programs. I understand this very well, because I have also experienced it, which may be the reason why some people have always liked to talk about DCL on the Internet. If it is placed before Java 5, it is understandable to explain DCL from the compiler perspective. The jmm (Memory Model) of Java 5 has been greatly corrected, if we can only explain DCL from the compiler perspective, it would be an insult to Java. To know the biggest advantage of Java, we only need to consider a platform. You can ignore the vast majority of discussions about DCL on the Internet. Most of the time they do not know, except Doug
I don't believe who is more authoritative than Lea.

Many people do not understand DCL. It is not how complicated DCL is. On the contrary, they do not have enough basic knowledge. Therefore, I will start from the basics and then analyze DCL.

We all know that data competition occurs when two threads read/write (or write) a shared variable at the same time. So how can I know that data competition has taken place? I need to read the variable. there are usually two manifestations of data competition: first, reading old data, that is, reading data that was previously written, but not the latest. Second, read the value that has not been written before, that is, read garbage.

Old Data

To read the latest data written by another thread, jmm defines a series of rules. The most basic rule is to use synchronization. In Java, synchronization methods include synchronized and volatile. Here I will only involve syncrhonized. Please remember the following rules first. I will elaborate on them later.

Rule 1: You must synchronize all writes to the variable with all reads to read the latest data.

Let's take a look at the following code: Java code

  1. Public Class {
  2. Private int some;
  3. Public int another;
  4. Public int getsome () {return some ;}
  5. Public synchronized int getsomewithsync () {return some ;}
  6. Public void setsome (INT v) {some = V ;}
  7. Public synchronized void setsomewithsync (INT v) {some = V ;}
  8. }

Let's analyze the situations where one thread writes and the other thread reads. There are four cases in total. The first case is a = new A (). Other threads are not considered for the moment.

Case 1: read/write operations are not synchronized.

Thread1 Thread2
(1) A. setsome (13)  
  (2) A. getsome ()

In this case, even if thread1 writes some to 13 first, thread2 then reads some, can it read 13? In the absence of synchronous coordination, the results are uncertain. As shown in the figure, the two threads run independently, and jmm does not guarantee that one thread can see the value written by another thread. In this example, thread2 may read 0 (the initial value of some) instead of 13. Note: theoretically, even if thread2 writes some data to thread1 and waits for another 10 thousand years, it may still read the initial value of some, even though this is practically impossible.

Case 2: Write synchronization, read is not synchronized

Thread1 Thread2
(1) A. setsomewithsync (13)  
  (2) A. getsome ()

Case 3: Read synchronization and write Synchronization

Thread1 Thread2
(1) A. setsome (13)  
  (2) A. getsomewithsync ()

In both cases, thread1 and thread2 only lock reading or writing some, which does not play any role, just like [scenario 1, thread2 may still read some of the initial values 0. It can also be seen from the figure that thread1 and thread2 do not affect each other, and the locking of one party does not affect the continued operation of the other party. The figure also shows that the synchronization operation is equivalent to the lock operation when the synchronization starts and the unlock operation is executed when the synchronization ends.

Case 4: read/write Synchronization

Thread1 Thread2
(1) A. setsomewithsync (13)  
  (2) A. getsomewithsync ()

In case 4, when thread1 writes some data, thread2 waits for thread1 to finish writing, and it can see the modifications made to thread1. At this time, thread2 can ensure that it can read 13. In fact, thread2 not only shows the modifications to thread1 to some, but also any modifications made by thread1 before the modifications to some. More precisely, that is, the lock operation of one thread can see all the modifications made by the other thread before the unlock operation of the same object. Please pay attention to the red arrow in the figure. The arrow in the graph indicates the direction, and the changes made at the start of the arrow are always displayed at the end of the arrow. In this way,. some [thread2] can see lock [thread2], lock [thread2] can see unlock [thread1], and unlock [thread1] can see a again. some = 13 [thread1], you can see that some value is 13.

Let's take a look at a slightly more complex example:

Example 5

Thread1 Thread2
(1) A. Another = 5  
(2) A. setsomewithsync (13)  
  (3) A. getsomewithsync ()
(4) A. Another = 7  
  (5) A. Another

Thread2 will finally read the value of another? Will it read that the initial value of another is 0? After all, all accesses to another are not synchronized? No. It can be clearly seen that the another of thread2 should at least see the 5 written by thread1 before lock, but it cannot ensure that thread1 can see 7 written by unlock. Therefore, thread2 may read the value of another 5 or 7, but not 0. You may have discovered that if you remove the thread2 read A. Some operation in the figure, it is equivalent to an empty synchronization block, which has no effect on the conclusion. This indicates that empty synchronization blocks work. The compiler cannot optimize empty synchronization blocks without authorization, but you should be very careful when using empty synchronization blocks, generally, it is not the result you want. In addition, you must note that the unlock operation and lock operation must target the same object to ensure that the unlock operation can see the changes made before the lock operation.

Example 6: different locksJava code

  1. Class B {
  2. Private object lock1 = new object ();
  3. Private object lock2 = new object ();
  4. Private int some;
  5. Public int getsome (){
  6. Synchronized (lock1) {return some ;}
  7. }
  8. Public void setsome (INT v ){
  9. Synchronized (lock2) {some = V ;}
  10. }
  11. }

Thread1 Thread2
(1) B. setsome (13)  
  (2) B. getsome ()

In this case, although getsome and setsome are both locked, because they are different locks, one thread does not block the running of another thread. Therefore, thread2 does not guarantee reading some of the latest values written by thread1.

Now let's look at DCL:

Example 7: DCLJava code

  1. Public class lazysingleton {
  2. Private int somefield;
  3. Private Static lazysingleton instance;
  4. Private lazysingleton (){
  5. This. somefield = 201; // (1)
  6. }
  7. Public static lazysingleton getinstance (){
  8. If (instance = NULL) {// (2)
  9. Synchronized (lazysingleton. Class) {// (3)
  10. If (instance = NULL) {// (4)
  11. Instance = new lazysingleton (); // (5)
  12. }
  13. }
  14. }
  15. Return instance; // (6)
  16. }
  17. Public int getsomefield (){
  18. Return this. somefield; // (7)
  19. }
  20. }

Suppose thread1 calls getinstance () first. Because no thread has created a lazysingleton instance, it will create an instance S and return it. This is thread2 and then call getinstance (). When it runs to (2), since the read instance is not synchronized, it may read S or null (see scenario 2 ). Consider the case where it reads S, and draw a flowchart as follows:

Because thread2 has read S, getinstance () will return s immediately. This is no problem, but the problem occurs when it reads S. somefiled. It can be seen that thread2 does not have any synchronization, so it may not be able to see thread1 write somefield value 20. For thread2, it may read S. somefield is 0, which is the root problem of DCL. From the above analysis, we can also see why it is almost impossible to try to correct DCL but want to completely avoid synchronization.

Next, consider thread2 reading the case where the instance is null at (2) and draw a flowchart:

Next, thread2 will read the Instance value when there is a lock. At this time, it will ensure that it can read the s. The reason can be determined by referring to case 4 or the direction indicated by the arrows in the figure.

There are so many questions about DCL:

  1. Next we will consider thread2's reading in (2) That the instance is null. What will it get when it calls S. somefiled? Will it get 0?
  2. Why does DCL require double check? Can I remove the check at (4? If not, why?

Atomicity
Back to scenario 1, why do we say thread2 reads that some values can only be 0 or 13, but not others? This is determined by Java's atomicity of reading and writing int and reference. The so-called "atomicity" is an inseparable minimum unit, which should be easily understood by students with the concept of database transactions. When some = 13 is called, either the write is successful or the write fails. It is impossible to write half of the data. However, Java does not perform atomic operations on the read and write operations of double and long, which means that some extreme exceptions may occur. Example: Java code

  1. Public class c {
  2. Private/* volatile */long X; // (1)
  3. Public void setx (long v) {x = V ;}
  4. Public long getx () {return X ;}
  5. }

Thread1 Thread2
(1) c. setx (0x1122334400112233l)  
  (2) c. getx ()

Thread2 reading X values may be 0, 1122334400112233, or other unexpected values. In one case, if the JVM writes down 4 bytes to long and then 4 bytes to long, the value of X read may be 112233. However, we do not make such assumptions about the JVM. To ensure that the read and write operations on long or double are atomic, either volatile or synchronized is used. In the preceding example, If you cancel the volatile comment at (1), the value read from thread2 to X is either 0 or 1122334400112233. If synchronization is used, getx and setx must be synchronized as follows:

Java code
  1. Public class c {
  2. Private/* volatile */long X; // (1)
  3. Public synchronized void setx (long v) {x = V ;}
  4. Public synchronized long getx () {return X ;}
  5. }

Therefore, there are also rules on Atomicity (volatile is actually a synchronization ).

Rule 2: For the double and long variables, the atomicity of the double and long variables can be guaranteed only when all reads and writes are synchronized.

Sometimes we need to ensure the atomicity of a composite operation, so we can only use synchronized.

Java code
  1. Public class canvas {
  2. Private int curx, Cury;
  3. Public/* synchronized */getpos (){
  4. Return new int [] {curx, Cury };
  5. }
  6. Public/* synchronized */void moveTo (int x, int y ){
  7. Curx = X;
  8. Cury = y;
  9. }
  10. }

Thread1 Thread2
(1) c. moveTo (1, 1)  
(2) c. moveTo (2, 2)  
  (3) c. getpos ()

If there is no synchronization, the getpos of thread2 may get [1, 2], although this point may never appear. The reason for this is that when thread2 calls getpos (), curx has three possibilities: 0, 1, or 2. Similarly, Cury has three possibilities, so getpos () [], [], [], [], [], [], [], [], [], [2, 2] There are nine possibilities. To avoid this situation, you only need to set getpos () and moveTo as synchronization methods.

Summary

The above two symptoms of data competition are analyzed. The old data and non-atomic operations are caused by improper synchronization. These are actually quite basic knowledge, and synchronization can have two effects: one is to ensure that the latest data is read, and the other is to ensure the atomicity of operations, but most books place excessive emphasis on the latter, due to lack of understanding of the former, there are many misunderstandings about multithreading. If you want to learn more about Java threads, I only recommend Java concurrent programming design principles and patterns. In fact, I haven't written Java for a long time. These things are my knowledge two years ago. If you have any problems, please point out that you should never be polite.

Original article: http://www.iteye.com/topic/875420

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.