1. When will thread safety problems occur?
There will be no thread safety problems in a single thread, but in multi-threaded programming, it is possible to access the same resource at the same time. This resource can be various types of resources: a variable, an object, a file, a database table, etc. When multiple threads access the same resource at the same time, there will be a problem:
Since the process executed by each thread is uncontrollable, it is likely that the final result will be contrary to the actual wish or will directly lead to program errors.
Let's give a simple example:
Now there are two threads that read data from the network separately and then insert it into a database table, requiring that duplicate data cannot be inserted.
Then there must be two operations in the process of inserting data:
1) Check whether the data exists in the database;
2) If it exists, it will not be inserted; if it does not exist, it will be inserted into the database.
If the two threads are represented by thread-1 and thread-2 respectively, and at some point, thread-1 and thread-2 both read data X, this may happen:
thread-1 checks whether data X exists in the database, and thread-2 also checks whether data X exists in the database.
As a result, the result of the check of the two threads is that data X does not exist in the database, so both threads insert data X into the database table respectively.
This is a thread safety issue, that is, when multiple threads access a resource at the same time, the program run result is not the result you want to see.
Here, this resource is called: critical resource (also known as shared resource).
That is to say, when multiple threads access critical resources (one object, attributes in an object, a file, a database, etc.), thread safety issues may arise.
However, when multiple threads execute a method, local variables inside the method are not critical resources, because the method is executed on the stack, while the Java stack is thread-private, so there will be no thread safety issues.
2. How to solve thread safety issues?
So in general, how to solve thread safety problems?
Basically, when solving thread safety problems, all concurrency modes adopt the "serialized access to critical resources" solution, that is, at the same time, only one thread can access critical resources, also known as synchronous mutually exclusive access.
Generally speaking, a lock is added before the code that accesses the critical resource. After accessing the critical resource, the lock is released and other threads continue to access.
In Java, two ways are provided to implement synchronous mutex access: synchronized and Lock.
This article mainly talks about the use of synchronized, and the use of Lock is described in the next blog post.
Three.synchronized synchronization method or synchronization block
Before understanding the use of synchronized keywords, let’s first look at a concept: mutex locks, as the name suggests: locks that can achieve the purpose of mutex access.
To give a simple example: if a critical resource is added to a mutex, when one thread accesses the critical resource, the other threads can only wait.
In Java, each object has a lock mark (monitor), also known as a monitor. When multiple threads access an object at the same time, the thread can only access it if it acquires the lock of the object.
In Java, the synchronized keyword can be used to mark a method or code block. When a thread calls the object's synchronized method or accesses the synchronized code block, the thread obtains the object's lock. Other threads cannot access the method for the time being. Only when the method is executed or the code block is executed will the thread release the object's lock, and other threads can execute the method or code block.
The following are some simple examples to illustrate the use of synchronized keywords:
1. Synchronized method
In the following code, two threads call the insertData object to insert data:
public class Test { public static void main(String[] args) { final InsertData insertData = new InsertData(); new Thread() { public void run() { insertData.insert(Thread.currentThread()); }; }.start(); new Thread() { public void run() { insertData.insert(Thread.currentThread()); }; }.start(); } } class InsertData { private ArrayList<Integer> arrayList = new ArrayList<Integer>(); public void insert(Thread thread){ for(int i=0;i<5;i++){ System.out.println(thread.getName()+"Insert data"+i); arrayList.add(i); } }} At this time, the output result of the program is:
This shows that two threads execute the insert method at the same time.
If the keyword synchronized is added before the insert method, the run result is:
class InsertData { private ArrayList<Integer> arrayList = new ArrayList<Integer>(); public synchronized void insert(Thread thread){ for(int i=0;i<5;i++){ System.out.println(thread.getName()+"Insert data"+i); arrayList.add(i); } }}From the above output, the data inserted in Thread-1 is only performed after Thread-0 has been inserted. This shows that Thread-0 and Thread-1 execute insert methods sequentially.
This is the synchronized method.
However, there are a few points to note:
1) When a thread is accessing the synchronized method of an object, other threads cannot access the other synchronized methods of the object. This reason is very simple, because an object only has one lock. When a thread acquires the lock of the object, other threads cannot obtain the lock of the object, so they cannot access other synchronized methods of the object.
2) When a thread is accessing the synchronized method of an object, then other threads can access the non-synchronized method of the object. The reason for this is very simple. Accessing non-synchronized methods does not require the object's lock. If a method is not modified with the synchronized keyword, it means that it will not use critical resources, then other threads can access this method.
3) If a thread A needs to access the synchronized method fun1 of object object1, and another thread B needs to access the synchronized method fun1 of object object2, even if object1 and object2 are the same type), there will be no thread safety problem, because they are accessing different objects, so there is no mutual exclusion problem.
2. Synchronized code block
Synchronized code blocks are similar to the following form:
synchronized(syncObject) {
}
When this block of code is executed in a thread, the thread acquires the lock of the object synObject, making it impossible for other threads to access the block of code at the same time.
synObject can be this, representing the lock that acquires the current object, or it can be an attribute in the class, representing the lock that acquires the attribute.
For example, the insert method above can be changed to the following two forms:
class InsertData { private ArrayList<Integer> arrayList = new ArrayList<Integer>(); public void insert(Thread thread){ synchronized (this) { for(int i=0;i<100;i++){ System.out.println(thread.getName()+"Insert data"+i); arrayList.add(i); } } }}class InsertData { private ArrayList<Integer> arrayList = new ArrayList<Integer>(); private Object object = new Object(); public void insert(Thread thread){ synchronized (object) { for(int i=0;i<100;i++){ System.out.println(thread.getName()+"Insert data"+i); arrayList.add(i); } } }}As can be seen from the above, synchronized code blocks are much more flexible to use than synchronized methods. Because perhaps only part of the code in a method needs to be synchronized, if the entire method is synchronized at this time, it will affect the program execution efficiency. This problem can be avoided by using synchronized code blocks. Synchronized code blocks can only synchronize where synchronization is required.
In addition, each class also has a lock, which can be used to control concurrent access to static data members.
And if a thread executes the non-static synchronized method of an object, and another thread needs to execute the static synchronized method of the class to which the object belongs, there will be no mutual exclusion at this time, because accessing the static synchronized method occupies a class lock, while accessing the non-static synchronized method occupies an object lock, so there is no mutual exclusion.
You will understand by looking at the following code:
public class Test { public static void main(String[] args) { final InsertData insertData = new InsertData(); new Thread(){ @Override public void run() { insertData.insert(); } }.start(); new Thread(){ @Override public void run() { insertData.insert1(); } }.start(); } } class InsertData { public synchronized void insert(){ System.out.println("Execute insert"); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Execute insert1"); } public synchronized static void insert1() { System.out.println("Execute insert1"); System.out.println("Execute insert1"); }} Execution results;
The insert method is executed in the first thread, which will not cause the second thread to block the insert1 method.
Let's take a look at what the synchronized keyword does. Let's decompile its bytecode. The decompiled bytecode of the following code is:
public class InsertData { private Object object = new Object(); public void insert(Thread thread){ synchronized (object) { } } public synchronized void insert1(Thread thread){ } public void insert2(Thread thread){ }}From the bytecode obtained by decompiling, it can be seen that the synchronized code block actually has two instructions: monitorenter and monitorexit. When the monitorenter instruction is executed, the object's lock count will be increased by 1, while when the monitorexit instruction is executed, the object's lock count will be reduced by 1. In fact, this is very similar to the PV operation in the operating system. The PV operation in the operating system is used to control multiple threads to access critical resources. For synchronized method, the thread in execution recognizes whether the method_info structure of the method has the ACC_SYNCHRONIZED flag setting, and then it automatically acquires the object's lock, calls the method, and finally releases the lock. If an exception occurs, the thread automatically releases the lock.
One thing to note: For synchronized methods or synchronized code blocks, when an exception occurs, the JVM will automatically release the lock occupied by the current thread, so there will be no deadlock due to the exception.
3. Some other notable things about synchronized
1. The difference between synchronized and static synchronized
synchronized locks the current instance of the class to prevent other threads from accessing all synchronized blocks of the instance of the class at the same time. Note that this is the "current instance of the class", and there is no such constraint on two different instances of the class. Then static synchronized happens to control access to all instances of the class. static synchronized restricts threads to access all instances of the class in jvm at the same time and access the corresponding code quickly. In fact, if there is synchronized in a method or a code block in a class, then after generating an instance of this class, the class will have a fast monitoring, and the thread concurrent access to the modified instance synchronized is protected quickly. static synchronized is the public one of the monitoring, which is the difference between the two. That is, synchronized is equivalent to this.synchronized, and static synchronized is equivalent to Something.synchronized.
A Japanese author, Jie Chenghao's "Java Multithreaded Design Pattern" has a column like this:
pulbic class Something(){ public synchronized void isSyncA(){} public synchronized void isSyncB(){} public static synchronized void cSyncA(){} public static synchronized void cSyncB(){} } Then, if two instances a and b of Something class are added, why can the following group of methods be accessed simultaneously by more than one thread? axisSyncA() and x.isSyncB()
bxisSyncA() and y.isSyncA()
cxcSyncA() and y.cSyncB()
dxisSyncA() and Something.cSyncA()
Here, it is clear that it can be judged:
a, both access to the synchronized domain of the same instance, so it cannot be accessed at the same time. B is for different instances, so it can be accessed at the same time. Because it is static synchronized, different instances will still be restricted, which is equivalent to Something.isSyncA() and Something.isSyncB(), so it cannot be accessed at the same time.
So, what about d?, the answer in the book can be accessed simultaneously. The reason for the answer is that synchronzied is that the instance method and synchronzied class method are different from the lock.
Personal analysis means that synchronized and static synchronized are equivalent to two gangs, each of which has its own control, and there is no constraint on each other and can be accessed at the same time. It is not clear how synchronzied is implemented in Java internal design.
Conclusion: A: synchronized static is the scope of a certain class. Synchronized static cSync{} prevents multiple threads from accessing the synchronized static method in this class at the same time. It works on all object instances of a class.
B: synchronized is the scope of an instance. synchronized isSync(){} prevents multiple threads from accessing the synchronized method in this instance at the same time.
2. The difference between synchronized method and synchronized code fast
There is no difference between synchronized methods(){} and synchronized (this){}, but synchronized methods(){} is convenient for reading comprehension, while synchronized (this){} can more accurately control conflict restrict access areas, and sometimes perform more efficiently.
3. Synchronized keyword cannot be inherited