0. Pioneering question code
The following code demonstrates a counter where two threads accumulate i at the same time, each performing 1000,000 times. The result we expect is definitely i=2000000. However, after we execute it many times, we will find that the value of i will always be less than 2000000. This is because when two threads write i at the same time, the result of one of the threads will overwrite the other.
public class AccountingSync implements Runnable { static int i = 0; public void increase() { i++; } @Override public void run() { for (int j = 0; j < 1000000; j++) { increase(); } } public static void main(String[] args) throws InterruptedException { AccountingSync accountingSync = new AccountingSync(); Thread t1 = new Thread(accountingSync); Thread t2 = new Thread(accountingSync); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); }} To fundamentally solve this problem, we must ensure that multiple threads must be fully synchronized when operating i. That is to say, when thread A writes i, thread B not only cannot write, but also cannot read it.
1. The role of synchronized keywords
The function of the keyword synchronized is actually to realize synchronization between threads. Its job is to lock the synchronized code, so that only one thread can enter the synchronization block at a time, thereby ensuring the security between threads. Just like in the above code, the i++ operation can only be executed by another thread at the same time.
2.Usage of synchronized keywords
Specify object lock: lock the given object, enter the synchronous code block to obtain the lock of the given object
Directly acting on the instance method: it is equivalent to locking the current instance. Entering the synchronous code block, you must obtain the lock of the current instance (this requires that when creating Thread, you must use the same Runnable instance)
Directly acting on static methods: it is equivalent to locking the current class. Before entering the synchronous code block, you must obtain the lock of the current class.
2.1 Specify the object to lock
The following code applies synchronized to a given object. There is a note here that the given object must be static, otherwise we will not share the object with each other every time we new a thread, so the meaning of locking will no longer exist.
public class AccountingSync implements Runnable { final static Object OBJECT = new Object(); static int i = 0; public void increase() { i++; } @Override public void run() { for (int j = 0; j < 1000000; j++) { synchronized (OBJECT) { increase(); } } } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new AccountingSync()); Thread t2 = new Thread(new AccountingSync()); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); }} 2.2 Directly act on the instance method
The synchronized keyword acts on the instance method, that is, before entering the increase() method, the thread must obtain the lock of the current instance. This requires us to use the same Runnable object instance when creating the Thread instance. Otherwise, the locks of the thread are not on the same instance, so there is no way to talk about the locking/synchronization issue.
public class AccountingSync implements Runnable { static int i = 0; public synchronized void increased() { i++; } @Override public void run() { for (int j = 0; j < 1000000; j++) { increase(); } } public static void main(String[] args) throws InterruptedException { AccountingSync accountingSync = new AccountingSync(); Thread t1 = new Thread(accountingSync); Thread t2 = new Thread(accountingSync); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); }} Please pay attention to the first three lines of the main method to illustrate the correct usage of the keywords on the instance method.
2.3 Directly acting on static methods
To apply the synchronized keyword to the static method, there is no need to use the two threads to point to the same Runnable method as in the above example. Because the method block needs to request the lock of the current class, not the current instance, the threads can still be synchronized correctly.
public class AccountingSync implements Runnable { static int i = 0; public static synchronized void increased() { i++; } @Override public void run() { for (int j = 0; j < 1000000; j++) { increase(); } } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new AccountingSync()); Thread t2 = new Thread(new AccountingSync()); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); }}3. Incorrect locking
From the example above, we know that if we need a counter application, in order to ensure the correctness of the data, we will naturally need to lock the counter, so we may write the following code:
public class BadLockOnInteger implements Runnable { static Integer i = 0; @Override public void run() { for (int j = 0; j < 1000000; j++) { synchronized (i) { i++; } } } public static void main(String[] args) throws InterruptedException { BadLockOnInteger badLockOnInteger = new BadLockOnInteger(); Thread t1 = new Thread(badLockOnInteger); Thread t2 = new Thread(badLockOnInteger); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); }}When we run the above code, we will find that the output i is very small. This means that the thread is not safe.
To explain this problem, we need to start with Integer: In Java, Integer is an invariant object. Like String, once an object is created, it cannot be modified. If you have an Integer=1, then it will always be 1. What if you want this object=2? You can only recreate an Integer. After each i++, it is equivalent to calling the valueOf method of Integer. Let's take a look at the source code of Integer's valueOf method:
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i);} Integer.valueOf() is actually a factory method, which tends to return a new Integer object and copy the value to i;
Therefore, we know the reason for the problem. Since between multiple threads, since i++ comes after i, i points to a new object, the thread may load different object instances every time it is locked. The solution is very simple. You can solve it by using one of the three synchronize methods above.