Recently, I came across a question called the most difficult Java interview question in history. This question gave me some new thoughts about thread safety. Let me share this question with you:
**public** **class** **TestSync2** **implements** **Runnable** { **int** b = 100; **synchronized** **void** **m1**() **throws** InterruptedException { b = 1000; Thread.sleep(500); //6 System.out.println("b=" + b); } **synchronized** **void** **m2**() **throws** InterruptedException { Thread.sleep(250); //5 b = 2000; } **public** **static** **void** **main**(String[] args) **throws** InterruptedException { TestSync2 tt = **new** TestSync2(); Thread t = **new** Thread(tt); //1 t.start(); //2 tt.m2(); //3 System.out.println("main thread b=" + tt.b); //4 } @Override **public** **void** **run**() { **try** { m1(); } **catch** (InterruptedException e) { e.printStackTrace(); }}}Copy the code
I recommend that you try to find out what the answer to this question is before you read the answer below. When I first started to see this problem, the first reaction I wipe lai, which old iron came up with this problem, such a chaotic code call, is really surprised for heaven. Start () and.run () are not familiar with. Start () and.run (), so the main thread will be run after.start().
main thread b=2000
b=2000
Copy the code
Advanced error: knowing start() but ignoring or not knowing synchronized, wondering what sleep() is for, and probably getting the following answer:
main thread b=1000
b=2000
Copy the code
In a word, I asked a lot of people, and most of them couldn’t get the correct answer at the first time. In fact, the correct answer is as follows:
main thread b=2000
b=1000
or
main thread b=1000
b=1000
Copy the code
Before explaining this answer, this kind of question actually encountered a lot in the interview, remember again learn C++ time, test address, pointer, learn Java time again in test I ++,++ I, “a” == b = True? This kind of problem is common, presumably we do this kind of problem know to rely on rote memorization is not solved, because this kind of change is too much, so to do this kind of ambiguous topic, must be able to its meaning, square solution. Especially multithreading, if you do not know its principle, not only in the interview can not pass, even if the lucky pass, in the work how can not handle thread safety problems, can only lead to your company losses.
There are two points in this question:
- synchronized
- New,runnable(thread.start()),running,blocking(thread.sleep ())
If you are not familiar with these students don’t worry, I will cover the following, let me explain the whole process:
- Create a new thread, t, which is in the new state.
- Call t.start() to bring the thread to the runnable state.
- We know that thread T is in runnable state and has not yet been scheduled by the CPU, but our tt.m2() is our local method code, so it must be tt.m2() first.
- Execute tt.m2() to enter the synchronized block and start executing the code, where sleep() does nothing but confuse the field of view (b=2000).
- When executing tt.m2(). There are two cases:
Case A: It is possible that thread T is already executing, but since M2 entered the synchronized code block first, t is blocked, and the main thread will execute the output. At this point, there is A debate about who executes first. If t is executed first or the main thread is executed first, it must be output first. If t thread is not blocked, it must be too late to dispatch to the CPU. Many people forget that synchronized actually made many optimizations after 1.6, among which there is a spin lock, which can ensure that there is no need to give up CPU. It may happen that this part of time coincides with the main thread output, and may happen before it. B equals 1000 first, In this case, the main thread output actually has two scenarios. Two thousand or a thousand.
Case B: It is possible that t has not been executed, and t.m2() will be executed as soon as it is executed. At this time, there are still two cases. B = 2000 or 1000
6. In either case, the t thread must end up printing 1000 because there is no place to modify 1000.
The entire process is as follows:
A Java interview question called “the most difficult ever” triggered by thread safety thinking, master?
2. Thread safety
For the code of the above problem, although it is difficult to appear in our actual scene, but I am sure that some colleagues write similar, then it may be your own pit, so I want to talk about some thread safety for this.
2.1 What is thread safety
Let’s paraphrase Java Concurrency in Practice: When multiple threads access to an object, if don’t have to consider these threads in the runtime environment of scheduling and execution alternately, also do not need to undertake additional synchronization, or in any other coordinated operation of the caller, call the object’s behavior can get the right results, that the object is thread-safe.
We can learn from the above:
- In what environment: Multi-thread environment.
- In what operation: multiple threads are scheduled and executed alternately.
- What happens: The right results can be obtained.
- Who: Thread-safe is used to describe whether an object is thread-safe.
2.2 Thread Safety
According to the security of Java shared objects, thread safety can be divided into five levels: immutable, absolute thread safety, relative thread safety, thread compatibility, and thread antagonism:
2.2.1 immutable
An Immutable object in Java is necessarily thread-safe because scheduling and alternate execution of threads does not change the object. Custom constants are also immutable, and final and objects ina constant pool are immutable as well.
In Java, the common enumeration class String is immutable. The same enumeration class used to implement the singleton pattern is inherently thread-safe. In a String, you can call replace() and subString() without changing its original value
2.2.2 Absolute thread safety
Let’s take a look at Brian Goetz’s “Java Concurrency In action” definition: when multiple threads access a class, the class behaves correctly regardless of how the runtime environment is scheduled or how those threads alternate, and without any additional synchronization or coordination in the calling code. So this class is called thread-safe.
Brian Goetz’s absolute thread-safe class definition is very strict, and implementing an absolute thread-safe class often requires a high, and sometimes unrealistic, price, as Zhiming Chou explains in Understanding the Java Virtual Machine. Vectorget and Remove are synchronized modifications, but it shows that Vector is not completely thread-safe. Here’s a quick example:
**public** Object **getLast**(Vector list) {
**return** list.get(list.size() - 1);
}
**public** **void** **deleteLast**(Vector list) {
list.remove(list.size() - 1);
}
Copy the code
If we use multiple threads to execute the above code, although remove and GET are guaranteed to be synchronized, the problem will occur. It is possible that the last element has been removed, but list.size() has already been retrieved. In fact, get will throw an exception because the element has already been removed.
2.2.3 Relatively safe
Zhou zhiming thinks that this definition can be weakened to limit “the action of calling this object” to “the individual operation on the object”, so that a relatively thread-safe definition can be obtained. It needs to be thread-safe for individual operations on this object, so we don’t need to do extra operations when we call it, but for certain sequential calls, we need extra synchronization. We could change the above call to Vector to:
**public** synchronized Object **getLast**(Vector list) {
**return** list.get(list.size() - 1);
}
**public** synchronized **void** **deleteLast**(Vector list) {
list.remove(list.size() - 1);
}
Copy the code
So we add synchronization as the caller, and the Vector fits our relative safety.
2.2.4 Thread Compatibility
Thread-compatible means that objects are not thread-safe, but can be synchronized correctly on the calling side, such as an ArrayList that can be locked to behave like a Vector.
2.2.5 Thread Confrontation
Thread opposition is code that cannot be used concurrently in a multithreaded environment, regardless of whether synchronization is taken on the calling end. Because the Java language is inherently multithreaded, thread-opposition code that excludes multithreading is rare, often harmful, and should be avoided.
2.3 How to Solve the thread safety problem
There are several common solutions to thread safety: mutex blocking (pessimistic, locking), non-blocking synchronization (like optimistic locking, CAS), and no synchronization (well written code, no synchronization at all)
Synchronization is the process of ensuring that shared data is used by only one thread (or several, when using semaphore) at a time when multiple threads concurrently access the data.
2.3.1 Mutually exclusive Synchronization
Mutual exclusion is a pessimistic approach, because he is worried that someone will destroy his data whenever he visits, so he needs to use some means to make the data exclusive during this period, so that no one else can have access to it. Critical sections, Mutex, and Semaphore are the main implementations of Mutex. In Java, ReentrantLock and synchronized are commonly used for synchronization. In actual business, synchronized is recommended. In fact, the code in section 1 also uses synchronized. Why is synchronized recommended?
- If we use lock, we have to manually make the unlock() call, but many people may forget about it in the actual development process, so synchronized is recommended as an easy way to program lock.
- Synchronized optimized it after JDK1.6 and moved from biased locking, lightweight locking, spin-fit locking, and finally to heavyweight locking. A Lock is a weight Lock. Synchronized will also be optimized in future JDK releases. Lock also fails in performance convenience.
If you need wait interruptible, wait timeout, fair lock, etc. in your business, you can use this ReentrantLock.
Of course, in our Mysql database, the exclusive lock is also a mutex synchronization implementation. When the exclusive lock is added, no other transaction can access its data.
2.3.2 Non-blocking synchronization
Non-blocking synchronization is an optimistic, in optimistic means he will go to try, if no one in the competition, is successful, otherwise compensate is death cycle (usually retry or cycle after jump out many times), the mutex synchronization is the most important problem is the thread block and awakens the performance issues, and optimistic synchronization strategies to solve the problem.
However, the above problem is that the operation and the detection of whether there is competition must ensure atomicity, which requires the support of our hardware devices. For example, the CAS operation in Java is actually the instruction at the bottom of the hardware of the operation.
CAS operations are not available in Java applications until after JDK1.5. The CAS operations are wrapped in methods such as compareAndSwapInt() and compareAndSwapLong() in the sun.misc.Unsafe class, and the vm has special handling for these methods internally. The result of this just-in-time compilation is a platform-dependent processor CAS, with no method calls or, if you like, unconditional inlining
2.3.3 without synchronization
Synchronization is not necessary to be thread-safe; there is no cause-and-effect relationship. Synchronization is only a means of ensuring correctness of shared data contention. If a method does not involve sharing data, it does not need any synchronization to ensure correctness, so some code is inherently field safe. It is generally divided into two categories:
- Reentrant code: Also known as pure code, reentrant code can be broken at any time, and the results of reentrant code are generally predictable:
**public** **int** **sum**(){
**return** 1+2;
}
Copy the code
This code, for example, is reentrant code, but it doesn’t appear much in our own code
- Thread-local storage: This is generally the method we use most, either by keeping the class stateless and all variables in our methods, or by using ThreadLocal.
2.4 Some other thread safety lessons
It’s all pretty official, but here’s what I learned from some real experience:
- When we use some object as a singleton, we need to determine whether the object is thread-safe: for example, when we use SimpleDateFormate, many beginners do not care to use it as a singleton as a utility class, resulting in our business exception. Refer to my other post: Can you really convert dates in Java?
- If you find that it is not a singleton, you need to replace it, such as HashMap with ConcurrentHashMap and Queue with ArrayBlockingQueue.
- Note deadlocks, if the use of lock must remember to release the lock, at the same time the use of lock order must pay attention to, here is not only said to be A single lock, also want to say distributed lock, must pay attention to: A line first lock A then lock B, another line first lock B then lock A this situation. Generally speaking, a timeout period is added to a distributed lock to avoid deadlock caused by lock release failure due to network problems.
- Lock granularity: the same is not only said stand-alone lock, but also includes distributed lock, not convenient to directly from the entry method, without analysis on the start of the lock, which will seriously affect the performance. The same can not be too fine-grained, a single lock will increase context switching, distributed locks will increase network calls, both of which will lead to our performance degradation.
- Appropriate introduction of optimistic locking: such as we have a demand to the user processing, in order to prevent more buckle, this time will use pessimistic lock to lock, but the efficiency is lower, because the user deductions actually buckle is less at the same time, we can use optimistic locking and version fields in the user’s account table to add, query version first, Then check whether the current version is consistent with the database version during the update. If the version is consistent, the update will prove that the database has been deducted.
- If you want to use non-thread-safe objects in a multi-threaded environment, the data can be created in a ThreadLocal or just in a method. Our ArrayList is not thread-safe, List List = new ArrayList() ¶ List List = new ArrayList() ¶