Record the pitfalls caused by the synchronized lock string and then talk about the Java string, synchronizedjava

Source: Internet
Author: User

Record the pitfalls caused by the synchronized lock string and then talk about the Java string, synchronizedjava

Problem description

The business has a requirement. I will describe the problem as follows:

Access a foreign website through the proxy IP address N, each IP address corresponds to a fixed website n cookie, COOKIE has a failure time.

In concurrency, there is a certain policy to get the IP address. After obtaining the IP address, take the COOKIE corresponding to the IP address. If the COOKIE exceeds the expiration time, the script is called to access the website N to obtain the data once.

To prevent multiple threads from obtaining the same IP address, the COOKIE corresponding to the IP address becomes invalid, and the script is called to update the COOKIE, the IP address is locked. To ensure the global uniqueness of the lock, a prefix is added to the lock and synchronized (lock) {} is used to lock the lock prefix + IP address ", this ensures that the same IP address is obtained by multiple threads, and only one IP address updates the COOKIE.

I don't know whether the problem is clear or not. It's okay to write a test code:

Public class StringThread implements Runnable {private static final String LOCK_PREFIX = "XXX ---"; private String ip; public StringThread (String ip) {this. ip = ip ;}@ Override public void run () {String lock = buildLock (); synchronized (lock) {System. out. println ("[" + JdkUtil. getThreadName () + "] started to run"); // sleep for 5 seconds simulate the script to call JdkUtil. sleep (1, 5000); System. out. println ("[" + JdkUtil. getThreadName () + "] ended running");} private String buildLock () {StringBuilder sb = new StringBuilder (); sb. append (LOCK_PREFIX); sb. append (ip); String lock = sb. toString (); System. out. println ("[" + JdkUtil. getThreadName () + "] constructed the lock [" + lock + "]"); return lock ;}}

Simply put, input an IP address and try to build a globally unique string to lock the string. The expected result is concurrency. For example, if five threads pass in the same IP address, the locks they build are all strings "XXX---192.168.1.1", then the five threads for the synchronized block should be executed serially, that is to say, after one operation is complete, run the other one, but this is not the case.

Write a test code and open five threads to check the effect:

public class StringThreadTest {    private static final int THREAD_COUNT = 5;        @Test    public void testStringThread() {        Thread[] threads = new Thread[THREAD_COUNT];        for (int i = 0; i < THREAD_COUNT; i++) {            threads[i] = new Thread(new StringThread("192.168.1.1"));        }                for (int i = 0; i < THREAD_COUNT; i++) {            threads[i].start();        }                for (;;);    }    }

The execution result is:

[Thread-1] built the lock [XXX---192.168.1.1] [Thread-1] started running [Thread-3] built the lock [XXX---192.168.1.1] [Thread-3] started running [Thread -4] built locks [XXX---192.168.1.1] [Thread-4] started running [Thread-0] built locks [XXX---192.168.1.1] [Thread-0] started running [Thread-2] built lock [XXX---192.168.1.1] [Thread-2] started running [Thread-1] ended running [Thread-3] ended running [Thread-4] ended running [Thread-4 [thread-0] ended running [Thread-2] ended running

See Thread-0, Thread-1, Thread-2, Thread-3, Thread-4 these five threads, although the build lock is the same "XXX-192.168.1.1 ", but the code is executed in parallel, which is not in line with our expectations.

On the one hand, it is true that I thought it was a problem with synchronization control in other parts of the Code. On the other hand, it also reflected that my understanding of the String is not deep enough, therefore, I wrote an article to record the problem and clarify the cause and solution of the problem.

 

Cause

Since this problem has occurred, we should start from the result and find the cause of the problem. Let's take a look at the synchronized code:

@ Overridepublic void run () {String lock = buildLock (); synchronized (lock) {System. out. println ("[" + JdkUtil. getThreadName () + "] started to run"); // sleep for 5 seconds simulate the script to call JdkUtil. sleep (1, 5000); System. out. println ("[" + JdkUtil. getThreadName () + "] stops running ");}}

BecauseWhen synchronized locks an object, ensure that code execution in the synchronized code block is a prerequisite for serial execution. The lock object is the sameTherefore, since multithreading is executed in parallel in the synchronized part, we can infer that the same IP address can be imported under multithreading, And the constructed lock string is not the same.

Next, let's look at the code for constructing strings:

Private String buildLock () {StringBuilder sb = new StringBuilder (); sb. append (LOCK_PREFIX); sb. append (ip); String lock = sb. toString (); System. out. println ("[" + JdkUtil. getThreadName () + "] constructed the lock [" + lock + "]"); return lock ;}

Lock is generated by StringBuilder. Let's take a look at the toString method of StringBuilder:

public String toString() {    // Create a copy, don't share the array    return new String(value, 0, count);}

The reason is here: although the string built by the buildLock () method is "XXX-192.168.1.1 ",The toString () method of StringBuilder is a new String each time.So the objects generated by buildLock are different objects.

 

How can this problem be solved?

The reason for the above problem is that every time the StringBuilder constructs new objects, how should we solve them? Here, the solution isSb. toString () followed by intern ()In the next section, let's talk about the reason, because I want to make a summary of the String to deepen my understanding of the String.

OK, the code is changed as follows:

1 public class StringThread implements Runnable {2 3 private static final String LOCK_PREFIX = "XXX ---"; 4 5 private String ip; 6 7 public StringThread (String ip) {8 this. ip = ip; 9} 10 11 @ Override12 public void run () {13 14 String lock = buildLock (); 15 synchronized (lock) {16 System. out. println ("[" + JdkUtil. getThreadName () + "] started to run"); 17 // sleep 5 seconds simulated script call 18 JdkUtil. sleep (5000); 19 System. out. println ("[" + JdkUtil. getThreadName () + "] ended running"); 20} 21} 22 23 private String buildLock () {24 StringBuilder sb = new StringBuilder (); 25 sb. append (LOCK_PREFIX); 26 sb. append (ip); 27 28 String lock = sb. toString (). intern (); 29 System. out. println ("[" + JdkUtil. getThreadName () + "] constructed the lock [" + lock + "]"); 30 31 return lock; 32} 33 34}

Let's take a look at the code execution result:

[Thread-0] built lock [XXX---192.168.1.1] [Thread-0] started running [Thread-3] built lock [XXX---192.168.1.1] [Thread-4] built lock [XXX---192.168.1.1] [Thread-1] built lock [XXX---192.168.1.1] [Thread-2] built lock [XXX---192.168.1.1] [Thread-0] ended running [Thread-2] started running [thread-2] ended running [Thread-1] started running [Thread-1] ended running [Thread-4] started running [Thread-4] ended running [thread-3] started running [Thread-3] ended running

We can compare the execution results without the intern () method above. It is obvious that the five threads obtain the same lock, after a thread finishes executing the code in the synchronized code block, the next thread can execute the code. The whole execution is serialized.

 

Let's look at the String

There is a constant pool in the JVM memory area. About the allocation of the constant pool:

Strings are stored in the constant pool. Two types of string data are stored in the constant pool:

The String data in the constant pool has one feature:When data is retrieved, if the constant pool exists, the data in the constant pool is taken directly. If the constant pool does not exist, the data is written to the constant pool and the data in the constant pool is returned..

Therefore, back to our previous scenario, use StringBuilder to concatenate a string and return a new object each time. However, the intern () method is different:

"XXX-192.168.1.1" this string was created using the toString () method of StringBuilder, but because the intern () method was used, the first thread found that the constant pool was not "XXX-192.168.1.1 ", the constant pool has
"XXX-192.168.1.1", the subsequent thread found that the constant pool has "XXX-192.168.1.1", directly take the "XXX-192.168.1.1" in the constant pool ".

So no matter how many threads, as long as the "XXX-192.168.1.1", take out must be the same object, is the constant pool in the "XXX-192.168.1.1"

All of these are the functions of the String intern () method.

 

Postscript

After this article is completed, I have a little bit of emotion. Many people will think that a Java programmer can make good use of the framework and write the code flow without any bugs, studying underlying principles and virtual machines is useless. I don't know if I can give you some inspiration:

This business scenario is not complicated, and the entire code implementation is not very complicated, but it has a concurrency problem during running. If you do not have a solid foundation: You know the common methods in String, including indexOf, subString, and concat, as well as the very uncommon intern () methods. You do not know a little about JVM: JVM memory distribution, in particular, the constant pool does not look at the JDK source code: The toString () method of StringBuilder does not have some understanding about concurrency: when synchronized locks the code block, how can we ensure that multithreading is a problem in the code block that is executed in serial mode? This problem cannot be solved at all. Even we can say that we do not know how to analyze it.

Therefore, do not think that the underlying implementation principles of JVM and JDK source code are useless. On the contrary, these are the most valuable things for technicians to grow up.

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.