If your Java foundation is weak or you don’t have a good understanding of Java multithreading, please read this article "Learn the thread definition, state and properties of Java multithreading"
Synchronization has always been a difficult point for Java multi-threading, and it is rarely used when we are doing Android development, but this is not a reason why we are not familiar with synchronization. I hope this article can enable more people to understand and apply java synchronization.
In multi-threaded applications, two or more threads need to share access to the same data. This usually becomes a race condition if two threads access the same object and each thread calls a method that modifies the object.
The easiest example of competition conditions is: for example, train tickets are certain, but there are windows for selling train tickets everywhere, each window is equivalent to one thread, and so many threads share all the train ticket resource. And it cannot guarantee its atomicity. If two threads use this resource at a point in time, the train tickets they take out are the same (the seat number is the same), which will cause trouble for passengers. The solution is that when a thread wants to use the train ticket resource, we give it a lock, and after it finishes the work, we will give the lock to another thread that wants to use this resource. This way, the above situation will not occur.
1. Lock the object
The synchronized keyword automatically provides locks and related conditions. It is very convenient to use synchronized in most cases where explicit locks are required. However, when we understand the ReentrantLock class and conditional objects, we can better understand the synchronized keyword. ReentrantLock is introduced in JAVA SE 5.0. The structure of the code block is protected using ReentrantLock as follows:
mLock.lock();try{...} finally{mLock.unlock();}This structure ensures that only one thread enters the critical area at any time. Once a thread blocks the lock object, no other thread can pass the lock statement. When other threads call lock, they are blocked until the first thread releases the lock object. It is very necessary to put the unlocking operation in finally. If an exception occurs in the critical area, the lock must be released, otherwise other threads will be blocked forever.
2. Conditional object <br />When entering the critical area, it is found that it can only be executed after a certain condition is met. Use a conditional object to manage threads that have obtained a lock but cannot do useful work. Conditional objects are also called conditional variables.
Let's take a look at the following example to see why conditional objects are needed
Suppose in a scenario we need to use bank transfers, we first write the bank class, and its constructor needs to be transferred to the number of accounts and the amount of accounts.
public class Bank {private double[] accounts; private Lock bankLock; public Bank(int n,double initialBalance){ accounts=new double[n]; bankLock=new ReentrantLock(); for (int i=0;i<accounts.length;i++){ accounts[i]=initialBalance; } } }Next we want to withdraw money and write a withdrawal method. From is the transferor, to is the receiver, and amount transfer amount. As a result, we found that the transferor's balance is insufficient. If other threads save enough money to the transferor, the transfer can be successful. However, this thread has acquired the lock, which is exclusive, and other threads cannot acquire the lock to perform deposit operations. This is why we need to introduce conditional objects.
public void transfer(int from,int to,int amount){ bankLock.lock(); try{ while (accounts[from]<amount){ //wait } } finally { bankLock.unlock(); } }A lock object has multiple related condition objects. You can use the newCondition method to obtain a condition object. After we get the condition object, we call the await method, and the current thread is blocked and the lock is abandoned.
public class Bank {private double[] accounts; private Lock bankLock; private Condition condition; public Bank(int n,double initialBalance){ accounts=new double[n]; bankLock=new ReentrantLock(); //Get the condition object condition=bankLock.newCondition(); for (int i=0;i<accounts.length;i++){ accounts[i]=initialBalance; } } public void transfer(int from,int to,int amount) throws InterruptedException { bankLock.lock(); try{ while (accounts[from]<amount){ //block the current thread and give up the lock condition.await(); } } finally { bankLock.unlock(); } }} The thread waiting for the lock is essentially different from the thread calling the await method. Once a thread calls the await method, it will enter the wait set of that condition. When the lock is available, the thread cannot unlock immediately, but instead it is in a blocking state until another thread calls the signalAll method on the same condition. When another thread is ready to transfer money to our previous transferor, just call condition.signalAll(); this call will reactivate all threads waiting for this condition.
When a thread calls the await method, it cannot reactivate itself and hopes that other threads will call the signalAll method to activate itself. If no other threads activate the waiting thread, then deadlock will occur. If all other threads are blocked, and the last active thread calls await before unblocking other threads, it will also be blocked. No thread can unblock other threads and the program will be suspended.
Then when will signalAll be called? Normally, it should be beneficial to call signalAll when waiting for the thread's direction to change. In this example, when an account balance changes, the waiting thread should have the opportunity to check the balance.
public void transfer(int from,int to,int amount) throws InterruptedException { bankLock.lock(); try{ while (accounts[from]<amount){ //block the current thread and give up the lock condition.await(); } //Transfer operation... condition.signalAll(); } finally { bankLock.unlock(); } }When the signalAll method is called, a waiting thread is not activated immediately. It simply unblocks the waiting threads so that these threads can achieve access to the object by competing after the current thread exits the synchronous method. Another method is signal, which randomly unblocks a thread. If the thread still cannot run, it will be blocked again. If no other thread calls signal again, the system will be deadlocked.
3. Synchronized keywords
The Lock and Condition interfaces provide programmers with a high degree of locking control, however, in most cases, such control is not required, and a mechanism embedded within the Java language can be used. Starting from Java version 1.0, every object in Java has an internal lock. If a method is declared with the synchronized keyword, the lock of the object will protect the entire method. That is, to call this method, the thread must obtain the internal object lock.
in other words,
public synchronized void method(){}Equivalent to
public void method(){this.lock.lock();try{} finally{this.lock.unlock();} In the bank example above, we can declare the transfer method of the Bank class as synchronized instead of using a displayed lock.
There is only one related condition for the internal object lock. Wait enlargement is added to a thread to the waiting set. NotifyAll or notify methods unblock the waiting thread. In other words, wait is equivalent to calling condition.await(), notifyAll is equivalent to condition.signalAll();
Our example transfer method above can also be written like this:
public synchronized void transfer(int from,int to,int amount)throws InterruptedException{ while (accounts[from]<amount) { wait(); } //Transfer operation... notifyAll(); }You can see that using the synchronized keyword to write code is much simpler. Of course, to understand this code, you must understand that each object has an internal lock and that the lock has an internal condition. The lock manages those threads that try to enter the synchronized method, and the conditions manages those threads that call wait.
4. Synchronous blocking <br />Above we said that every Java object has a lock, and a thread can call the synchronization method to obtain the lock, and there is another mechanism to obtain the lock. By entering a synchronization block, when the thread enters the following form of blocking:
synchronized(obj){}So he got the obj's lock. Let's take a look at the Bank class
public class Bank {private double[] accounts;private Object lock=new Object(); public Bank(int n,double initialBalance){ accounts=new double[n]; for (int i=0;i<accounts.length;i++){ accounts[i]=initialBalance; } } public void transfer(int from,int to,int amount){ synchronized(lock){ //Transfer operation... } }}Here, lock object creation is simply used to use the locks held by each Java object. Sometimes developers use an object's lock to implement additional atomic operations, called client locking. For example, the Vector class, its methods are synchronous. Now assume that the bank balance is stored in Vector
public void transfer(Vector<Double>accounts,int from,int to,int amount){ accounts.set(from,accounts.get(from)-amount); accounts.set(to,accounts.get(to)+amount;}The get and set methods of the Vecror class are synchronous, but this has not helped us. After the first call to get is completed, it is entirely possible that one thread is denied the right to run in the transfer method, so another thread may have stored different values in the same storage location, but we can intercept this lock
public void transfer(Vector<Double>accounts,int from,int to,int amount){ synchronized(accounts){ accounts.set(from,accounts.get(from)-amount); accounts.set(to,accounts.get(to)+amount; }}Client locking (synchronous code blocks) is very fragile and is usually not recommended. Generally, it is best to use the classes provided under the java.util.concurrent package, such as blocking queues. If the synchronization method is suitable for your program, please try to use the synchronization method. It can reduce the number of code written and reduce the chance of errors. If you need to use the unique features provided by the Lock/Condition structure, use Lock/Condition only.
The above is all about this article, I hope it will be helpful to everyone's learning.