Welcome to The fourth article in a series called “King’s Concurrency”.
In the previous article, “Double-edged Sword: Understanding the Security Issues brought by Multithreading,” we covered the thread safety issues in multithreaded situations. This article takes this problem as a background and shows you how to solve it by using the synchronized keyword. Of course, in the bronze stage, we still do not describe the principle behind it too much, but the focus is to experience and understand its use first.
Synchronized
Who defeated the master
In the canyon, the economic benefits of defeating the master are high. Therefore, when conditions permit, everyone will compete to defeat the master. As a result, Nezha and the rival Lanling King began to fight for the supremacy. According to the rules, the winner is the one who makes the final blow against the master.
Assuming the master’s initial health is 100, let’s simulate it in code:
public class Master {
// The master's initial health
private int blood = 100;
// Health is reduced by 5 per hit
public int decreaseBlood(a) {
blood = blood - 5;
return blood;
}
// Determine if the master is still alive by health
public boolean isAlive(a) {
return blood > 0; }}Copy the code
We defined two threads, Nezha and Lanling King, to attack the master at the same time:
public static void main(String[] args) {
final Master master = new Master();
Thread neZhaAttachThread = new Thread() {
public void run(a) {
while (master.isAlive()) {
try {
int remainBlood = master.decreaseBlood();
if (remainBlood == 0) {
System.out.println("Nezha has defeated the master!"); }}catch(InterruptedException e) { e.printStackTrace(); }}}}; Thread lanLingWangThread =new Thread() {
public void run(a) {
while (master.isAlive()) {
try {
int remainBlood = master.decreaseBlood();
if (remainBlood == 0) {
System.out.println("The Lanling King has defeated the master!"); }}catch(InterruptedException e) { e.printStackTrace(); }}}}; neZhaAttachThread.start(); lanLingWangThread.start(); }Copy the code
Here’s the result:
The Lanling King has defeated the master! Nezha has defeated the master! Process finished with exit code 0Copy the code
Both of them were in control! Obviously, we can’t accept this result. However, a closer look at the code shows that this surprising result is not surprising at all. The two threads made an error in the concurrent subtraction of blood because the code did not have the necessary concurrency security controls.
Of course, the solution is simply to add the synchronized keyword to the decreaseBlood method:
public synchronized int decreaseBlood(a) {
blood = blood - 5;
return blood;
}
Copy the code
Why does the synchronized keyword suffice? This is where you need to look down to understand locking and synchronization in Java.
Synchronized
1. Understand locks in Java objects
Before we get to synchronized, let’s briefly understand the concept of lock. In Java, every object has a lock. When multiple threads need to access an object, they need to obtain permission by acquiring a lock. Only the thread that acquired the lock can access the object, and other threads enter a wait state, waiting for the other threads to release the lock. As shown in the figure below:
2. Understand the synchronized keyword
According to the Official Sun documentation, the synchronized keyword provides a simple strategy to prevent thread interference and memory consistency errors: if an object is visible to multiple threads, all reads and writes to that object’s variables (except those with final modifications) need to be done through synchronized.
You may have noticed two key nouns:
- Thread Interference: Interference occurs when two operations running in different threads but affecting the same data interleave. This means that the two operations consist of multiple steps, and the sequence of steps overlaps;
- Memory Consistency Errors: Memory Consistency Errors occur when the views corresponding to the same data in different threads are inconsistent. The causes of memory consistency errors are complex, and fortunately, we don’t need to understand them in detail, just strategies to avoid them.
From a race point of view, thread interference corresponds to read-modify-write, while memory consistency errors correspond to check-then-act.
The combination of lock and synchronized means that lock is the basic mechanism for multi-threaded security, and synchronized is an implementation of the lock mechanism.
Synchronized
1. Use synchronized in instance methods
public synchronized int decreaseBlood(a) {
blood = blood - 5;
return blood;
}
Copy the code
Notice the synchronized field in this code, which indicates that the current method can be accessed by only one thread at a time. In addition, because the current method is an instance method, if there are multiple instances of the object, the different instances can be accessed by different threads without any collaboration between them.
However, as you might have thought, if there are two synchronized methods in the current thread, can different threads access different synchronized methods?
The answer is: no.
This is because synchronous methods within each instance can be accessed by only one thread.
2. Synchronized is used in static methods
public static synchronized int decreaseBlood(a) {
blood = blood - 5;
return blood;
}
Copy the code
Unlike instance method synchronized, static method synchronized is based on the class to which the current method belongs, that is, master.class, and each class has only one class object on the VIRTUAL machine. Thus, for the same class, only one thread at a time can access static synchronized methods.
When a class contains multiple static synchronized methods, only one thread can access the methods at a time.
Note: As you can see from the use of synchronized in instance methods and static methods, whether or not a synchronized method can allow access to other threads depends on the parameters of synchronized. Only one thread is allowed to access each different parameter at a time. With this knowledge, the following two usages are easy to understand.
3. Synchronized is used in code blocks for instance methods
public int decreaseBlood(a) {
synchronized(this) {
blood = blood - 5;
returnblood; }}Copy the code
In some cases, you don’t need to use synchronized at the entire method level, since it’s too granular to block. In this case, synchronized is a good choice for code blocks, as shown in the code above.
As mentioned earlier, synchronized’s concurrency limits depend on its parameters, which in this case is this, the instance object of the current class. In the preceding public synchronized int decreaseBlood(), the synchronized argument is also an instance object of the current class. Therefore, the following two pieces of code are equivalent:
public int decreaseBlood(a) {
synchronized(this) {
blood = blood - 5;
returnblood; }}public synchronized int decreaseBlood(a) {
blood = blood - 5;
return blood;
}
Copy the code
Use synchronized in code blocks for static methods
Similarly, the following two methods have the same effect.
public static int decreaseBlood(a) {
synchronized(Master.class) {
blood = blood - 5;
returnblood; }}public static synchronized int decreaseBlood(a) {
blood = blood - 5;
return blood;
}
Copy the code
A. synchronized B. synchronized
We’ve already covered several common uses of synchronized. Without having to memorize them, remember that synchronized can take any non-null object as an argument, and that each argument can be accessed by only one thread at a time. In addition, there are some practical Tips you can pay attention to:
- In Java
synchronized
The keyword is used to resolve synchronization when multiple threads access a shared resource to resolveThread interferenceandMemory consistencyProblem; - You can go throughCode blockorMethod (method)To use the
synchronized
Key words; synchronized
Based on the principle ofLock in objectWhen the thread needs to entersynchronized
When decorating a method or code block, it needs to be done firstTo obtainLock and after executionThe release ofIt;- When a thread enters a non-static synchronous method, it obtains a lock on the Object level. When a thread enters a statically synchronized method, it obtains a lock on the Class level.
- if
synchronized
The objects used innull, will be thrownNullPointerException
Error; synchronized
Has some impact on the performance of the methodBecause the thread is waiting to acquire the lock;- use
synchronized
whenTry to use blocks of code, rather than the whole method, so as not to block the whole method; - Try not to use String and primitive types as arguments. This is because the JVM optimizes strings, primitive types, when processing them. For example, if you want to lock different strings, but the JVM thinks they are the same, this is obviously not what you want.
We’ll talk more about the underlying principles of synchronized, such as visibility, instruction ordering, and so on, in later stages.
That’s all for the text, congratulations on getting another star! ✨
The teacher’s trial
- Handwriting code experience
synchronized
‘.
Further reading and references
- “King concurrent course” outline and update progress overview
- Docs.oracle.com/javase/tuto…
- Javagoal.com/synchroniza…
About the author
Pay attention to the public number [technology 8:30], timely access to the article updates. Pass on quality technical articles, record the coming-of-age stories of ordinary people, and occasionally talk about life and ideals. 8:30 in the morning push author quality original, 20:30 in the evening push industry depth good article.
If this post helped you, feel free to like or follow.