0. Leading question code
The following code demonstrates a counter. Two threads perform cumulative operations on i at the same time, each executing 1,000,000 times. The result we expect is definitely i=2000000. But After we execute it multiple times, we will find that the value of i is always less than 2000000. This is because when two threads write to i at the same time, the result of one thread 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 are completely synchronized when operating on i. That is to say, when thread A writes to i , B thread cannot not only write, but also cannot read.
1. The role of the synchronized keyword
The function of the synchronized keyword is actually to achieve synchronization between threads . Its job is to lock the synchronized code so that only one thread can enter the synchronized block at a time, thus ensuring the security between threads. Just like in the above code, i++ can only operate on one thread at the same time. During execution.
2. Usage of synchronized keyword
Specify object lock: lock the given object, enter the synchronized code block to obtain the lock of the given object
Acts directly on instance methods: equivalent to locking the current instance. Entering the synchronization code block requires obtaining the lock of the current instance (this requires the same Runnable instance to be used when creating Thread)
Acts directly on static methods: equivalent to locking the current class. Before entering the synchronization code block, you must obtain the lock of the current class.
2.1 Lock the specified object
Below The code applies synchronized to a given object. One thing to note here is that the given object must be static, otherwise every time we create a new thread, the object will not be shared with each other, and the meaning of locking will be meaningless. Exists.
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 Acts directly on instance methods
synchronized The keyword acts on instance methods, which means that 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 a Thread instance. Otherwise, the thread's The locks are not on the same instance, so there is no way to talk about locking/synchronization issues.
public class AccountingSync implements Runnable { static int i = 0; public synchronized 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); } }
Please pay attention to the first three lines of the main method, describing the keywords The correct usage of acting on instance methods.
2.3 Acting directly on static methods
Use the synchronized keyword on static methods, so there is no need to use two The threads must point to the same Runnable method. Because the method block needs to request the lock of the current class, not the current instance, threads can still be synchronized correctly.
public class AccountingSync implements Runnable { static int i = 0; public static synchronized void increase() { 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. Wrong locking
From the above example, 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. Therefore, we may The following code will be written:
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 shows that the thread is not safe.
To explain this problem, we have to start with Integer: In Java, Integer is an immutable object. Like String, once the 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 the valueOf method of Integer:
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. It will tend to return a new Integer object and re-copy the value to i;