Synchronized is a commonly used lock. Synchronized locks are mutually exclusive and can only be acquired by one thread at a time, thus ensuring thread safety. Synchronized is also a reentrant lock. Synchronized common scope:
- Modifies normal synchronization methods to lock the current object;
- You can also decorate static methods that lock the class object of the current class.
- You can also modify the code block to lock the contents of the parentheses.
1. Synchronized Common synchronization method
1.1 Application Examples
The most common thread safety problem is the calculation of the object’s member variables. Let’s first look at what problems will occur in our calculation without synchronized.
@Slf4j
@Getter
public class HasSelfNum implements Runnable {
private int num = 0;
@Override
public void run(a) {
add();
}
public void add(a) {
for (int i = 0; i < 10; i++) {
num++;
log.info(Current thread :{},num = {}, Thread.currentThread().getName(), num);
try {
TimeUnit.SECONDS.sleep(1);
} catch(InterruptedException e) { e.printStackTrace(); }}}}Copy the code
Test method:
@Slf4j
public class Main {
public static void main(String[] args) throws Exception {
HasSelfNum hasSelfNum = new HasSelfNum();
Thread threadA = new Thread(hasSelfNum, "Thread A");
Thread threadB = new Thread(hasSelfNum, "Thread B");
threadA.start();
threadB.start();
threadA.join();
threadB.join();
log.info(Final num = {}, hasSelfNum.getNum()); }}Copy the code
Output result:
15:20:49.075Thread [B] the INFO com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread B, num =2
15:20:49.075[A] thread INFO com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num =1
15:20:50.080Thread [B] the INFO com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread B, num =3
15:20:50.080[A] thread INFO com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num =3
15:20:51.081Thread [B] the INFO com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread B, num =5
15:20:51.081[A] thread INFO com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num =5
15:20:52.082Thread [B] the INFO com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread B, num =6
15:20:52.082[A] thread INFO com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num =6
15:20:53.082Thread [B] the INFO com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread B, num =7
15:20:53.082[A] thread INFO com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num =7
15:20:54.083Thread [B] the INFO com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread B, num =8
15:20:54.083[A] thread INFO com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num =9
15:20:55.084[A] thread INFO com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num =10
15:20:55.084Thread [B] the INFO com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread B, num =10
15:20:56.085Thread [B] the INFO com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread B, num =12
15:20:56.085[A] thread INFO com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num =12
15:20:57.086Thread [B] the INFO com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread B, num =14
15:20:57.086[A] thread INFO com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num =13
15:20:58.086Thread [B] the INFO com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread B, num =15
15:20:58.087[A] thread INFO com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num =16
15:20:59.088[the main] INFO com. Sachin. Threadlearn. Sync. Sync1. Main - num = in the end16
Copy the code
According to the above experiment, in the case of not using synchronized, multiple threads may obtain the same data at the same time, causing errors in the subsequent calculation. Run it several times more, and the results of each calculation will be different, but most of them are incorrect. To ensure that the current thread can obtain the correct data, we introduce synchronized lock, and only one thread can obtain data calculation at a time:
public synchronized void add(a) {
for (int i = 0; i < 10; i++) {
num++;
log.info(Current thread :{},num = {}, Thread.currentThread().getName(), num);
try {
TimeUnit.SECONDS.sleep(1);
} catch(InterruptedException e) { e.printStackTrace(); }}}Copy the code
Test program calculation results:
15:25:35. 115 / thread A INFO com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num = 1 15:25:36. 120 thread [A] the INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num = 2 15:25:37. 121 thread [A] the INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num = 3 15:25:38. 122 thread [A] the INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num = 4 15:25:39. 122 thread [A] the INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num = 5 15:25:40. 123 thread [A] the INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num = 6 15:25:41. 124 thread [A] the INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num = 7 15:25:42. 125 thread [A] the INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num = 8 15:25:43. 125 thread [A] the INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num = 9 15:25:44. 126 thread [A] the INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num = 10. 15:25:45 127 [B] thread INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread B, num = 11 15:25:46. 128 thread [B] the INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread B, num = 12 15:25:47. 129 thread [B] the INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread B, num = 13 15:25:48. 130 thread [B] the INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread B, 15:25:49 num = 14 131 [B] thread INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread B, num = 15 15:25:50. 131 thread [B] the INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread B, num = 16 15:25:51. 132 thread [B] the INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread B, num = 17 15:25:52. 133 thread [B] the INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread B, num = 18 15:25:53. 133 thread [B] the INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread B, num = 19 15:25:54. 134 thread [B] the INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread B, num = 20 15:25:55. The 136 [the main] INFO Com. Sachin. Threadlearn. Sync. Sync1. Main - eventually num = 20Copy the code
According to the test results, synchronized locks ensure that threads are executed sequentially and the final results are correct. The join() method in the above test code is used to ensure that the main thread continues to run until the child thread is destroyed, so that the main thread does not output the result before the child thread is finished, resulting in an error output.
1.2 Verifying Locking the current object
Initially, we said that synchronized modifiers of ordinary methods lock the current object. To test this idea, we create a class with two synchronized methods, and then create two threads that call different methods:
@Slf4j
public class MyService {
public synchronized void methodA(a) {
try {
log.info("Current thread :{}, start calling method A", Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(5);
log.info("Current thread :{}, leaving method A", Thread.currentThread().getName());
} catch(InterruptedException e) { e.printStackTrace(); }}public synchronized void methodB(a) {
try {
log.info("Current thread :{}, start calling method B", Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(5);
log.info("Current thread :{}, leaving method B", Thread.currentThread().getName());
} catch(InterruptedException e) { e.printStackTrace(); }}}@AllArgsConstructor
public class ThreadA extends Thread {
private MyService service;
@Override
public void run(a) { service.methodA(); }}@AllArgsConstructor
public class ThreadB extends Thread {
private MyService service;
@Override
public void run(a) { service.methodB(); }}Copy the code
Test procedure:
public static void main(String[] args) {
MyService service = new MyService();
ThreadA threadA = new ThreadA(service);
ThreadB threadB = new ThreadB(service);
threadA.start();
threadB.start();
}
Copy the code
Test results:
15:40:19.011 [Thread-0] the INFO com. Sachin. Threadlearn. Sync. LockInstance. MyService - the current Thread: Thread -0Start calling method A15:40:24.015 [Thread-0] the INFO com. Sachin. Threadlearn. Sync. LockInstance. MyService - the current Thread: Thread -0, leaving method A15:40:24.016 [Thread-1] the INFO com. Sachin. Threadlearn. Sync. LockInstance. MyService - the current Thread: Thread -1Start calling method B15:40:29.017 [Thread-1] the INFO com. Sachin. Threadlearn. Sync. LockInstance. MyService - the current Thread: Thread -1BCopy the code
As you can see, the other thread must wait for the first thread to release the lock before it can acquire it, executing the program to show that they acquired the same lock as a Service object. If two threads have different objects in the constructor, they will acquire different locks and will not wait.
public static void main(String[] args) {
MyService service = new MyService();
ThreadA threadA = new ThreadA(service);
ThreadB threadB = new ThreadB(new MyService());
threadA.start();
threadB.start();
}
Copy the code
Test results:
15:44:07.772 [Thread-1] the INFO com. Sachin. Threadlearn. Sync. LockInstance. MyService - the current Thread: Thread -1Start calling method B15:44:07.772 [Thread-0] the INFO com. Sachin. Threadlearn. Sync. LockInstance. MyService - the current Thread: Thread -0Start calling method A15:44:12.776 [Thread-0] the INFO com. Sachin. Threadlearn. Sync. LockInstance. MyService - the current Thread: Thread -0, leaving method A15:44:12.776 [Thread-1] the INFO com. Sachin. Threadlearn. Sync. LockInstance. MyService - the current Thread: Thread -1BCopy the code
Based on the above example, you can see that synchronized locks the current object when applied to a normal instance method.
1.3 Reentrant verification
Synchronized locks are reentrant locks. A synchronized lock is a thread that acquires an object lock and then requests it again. This means that calling another synchronized method from within a synchronized method always acquires a lock. Below we use the program to verify:
@Slf4j
@Getter
public class HasSelfNum implements Runnable {
private int num = 0;
@Override
public void run(a) {
add();
}
public synchronized void add(a) {
for (int i = 0; i < 10; i++) {
num++;
printLog();
try {
TimeUnit.SECONDS.sleep(1);
} catch(InterruptedException e) { e.printStackTrace(); }}}public synchronized void printLog(a) {
log.info(Current thread :{},num = {}, Thread.currentThread().getName(), num); }}Copy the code
The method calculated before split into two, according to the above lock object verification, if two threads respectively call the two methods is needed to wait, but if the same thread has obtained the lock has not been released, to call another method, will not have to request the lock again, can be directly called. Test method:
HasSelfNum hasSelfNum = new HasSelfNum();
Thread threadA = new Thread(hasSelfNum, "Thread A");
threadA.start();
Copy the code
Test results:
15:47:00. 249 / thread A INFO com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num = 1 15:47:01. 253 thread [A] the INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num = 2 15:47:02. 253 thread [A] the INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num = 3 15:47:03. 253 thread [A] the INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num = 4 15:47:04. 254 thread [A] the INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num = 5 15:47:05. 254 thread [A] the INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num = 6 15:47:06. 255 thread [A] the INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num = 7 15:47:07. 256 thread [A] the INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num = 8 15:47:08. 257 thread [A] the INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num = 9 15:47:09. 258 thread [A] the INFO Com. Sachin. Threadlearn. Sync. Sync1. HasSelfNum - the current thread: thread A, num = 10Copy the code
As you can see, after the same thread has acquired the lock, it is always successful to call another synchronized method in one synchronized method.
2. Synchronized Static method
When synchronized modifys a static method, the class object is locked. Let’s test this idea by creating a class with a synchronized static method and a synchronized normal method, and then creating two threads that call different methods:
@Slf4j
public class MyService {
public synchronized static void methodA(a) {
try {
log.info("Start executing static method A");
TimeUnit.SECONDS.sleep(5);
log.info("Leave static method A");
} catch(InterruptedException e) { e.printStackTrace(); }}public synchronized void methodB(a) {
try {
log.info("Start executing instance method B");
TimeUnit.SECONDS.sleep(5);
log.info("Leave instance method B");
} catch(InterruptedException e) { e.printStackTrace(); }}}@AllArgsConstructor
public class ThreadA extends Thread {
private MyService service;
@Override
public void run(a) { service.methodA(); }}@AllArgsConstructor
public class ThreadB extends Thread {
private MyService service;
@Override
public void run(a) { service.methodB(); }}Copy the code
Test procedure:
public static void main(String[] args) {
MyService service = new MyService();
ThreadA threadA = new ThreadA(service);
ThreadB threadB = new ThreadB(service);
threadA.start();
threadB.start();
}
Copy the code
Test results:
15:20:55.192 [Thread-1] the INFO com. Sachin. Threadlearn. Sync. Sync2. Start the instance method B MyService -15:20:55.192 [Thread-0] the INFO com. Sachin. Threadlearn. Sync. Sync2. MyService - began to implement A static method15:21:00.197 [Thread-1] the INFO com. Sachin. Threadlearn. Sync. Sync2. MyService - leaving the instance method B15:21:00.197 [Thread-0] the INFO com. Sachin. Threadlearn. Sync. Sync2. MyService - leave A static methodCopy the code
The two threads start executing the corresponding method almost at the same time, indicating that the two threads do not acquire the same lock.
3. Synchronized modifies code blocks
Synchronized are modified in the above example the method, in this case, each thread calls the method, have to wait for the thread lock lock is released before he can perform, but, in some cases, the method only part of the need to use the lock, and the other does not exist multithreading call problem, in this case, We only need to use synchronized to modify the code blocks to be locked. Here are the specific uses:
@Slf4j
public class MyService implements Runnable {
private int num;
@Override
public void run(a) {
add();
}
private void add(a) {
log.info("Thread :{}, enter method", Thread.currentThread().getName());
synchronized (this) {
log.info("Thread :{}, enter sync code block", Thread.currentThread().getName());
for (int i = 0; i < 5; i++) {
num++;
}
log.info("Thread :{}, leaving synchronized code block", Thread.currentThread().getName());
}
log.info("Thread :{}, exit method", Thread.currentThread().getName()); }}Copy the code
Test code:
public static void main(String[] args) {
MyService service = new MyService();
Thread thread1 = new Thread(service);
Thread thread2 = new Thread(service);
thread1.start();
thread2.start();
}
Copy the code
Execution Result:
16:42:22.333 [Thread-0] the INFO com. Sachin. Threadlearn. Sync. SyncCode. MyService - Thread: Thread -0Enter the method16:42:22.333 [Thread-1] the INFO com. Sachin. Threadlearn. Sync. SyncCode. MyService - Thread: Thread -1Enter the method16:42:22.336 [Thread-0] the INFO com. Sachin. Threadlearn. Sync. SyncCode. MyService - Thread: Thread -0To enter the synchronized code block16:42:22.336 [Thread-0] the INFO com. Sachin. Threadlearn. Sync. SyncCode. MyService - Thread: Thread -0, leaving the synchronized code block16:42:22.336 [Thread-0] the INFO com. Sachin. Threadlearn. Sync. SyncCode. MyService - Thread: Thread -0, exit method16:42:22.336 [Thread-1] the INFO com. Sachin. Threadlearn. Sync. SyncCode. MyService - Thread: Thread -1To enter the synchronized code block16:42:22.336 [Thread-1] the INFO com. Sachin. Threadlearn. Sync. SyncCode. MyService - Thread: Thread -1, leaving the synchronized code block16:42:22.336 [Thread-1] the INFO com. Sachin. Threadlearn. Sync. SyncCode. MyService - Thread: Thread -1, exit methodCopy the code
As you can see from the results of the execution, both threads enter the method almost at the same time, whereas, in a synchronized block, the two threads execute sequentially. This part of the code is locked and can only be obtained by obtaining the lock. Synchronized locks the current and class objects, respectively, when modifying ordinary and static methods. Which part of a synchronized code block is locked? Synchronized has a parenthesis with “this” in it. In this case, the current object is locked, but myService.class can also be used to write myService.class or some other object; Create a class with three methods that use a synchronized lock block, a normal method, and a static method. Create three threads that call three methods:
@Slf4j
public class MyService {
public void methodA(a) {
synchronized (this) {
try {
log.info("Thread :{}, enter sync code block", Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(3);
log.info("Thread :{}, leaving synchronized code block", Thread.currentThread().getName());
} catch(InterruptedException e) { e.printStackTrace(); }}}public synchronized void methodB(a) {
try {
log.info("Thread :{}, enter synchronization method", Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(3);
log.info("Thread :{}, leaving synchronization method", Thread.currentThread().getName());
} catch(InterruptedException e) { e.printStackTrace(); }}public static synchronized void methodC(a) {
try {
log.info("Thread :{}, enter synchronous static method", Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(3);
log.info("Thread :{}, leaving synchronous static method", Thread.currentThread().getName());
} catch(InterruptedException e) { e.printStackTrace(); }}}Copy the code
Calling thread:
@AllArgsConstructor
public class ThreadA implements Runnable {
private MyService service;
@Override
public void run(a) { service.methodA(); }}... Omit the other two threadsCopy the code
Test method:
public class Main {
public static void main(String[] args) {
MyService service = new MyService();
new Thread(new ThreadA(service)).start();
new Thread(new ThreadB(service)).start();
new Thread(newThreadC(service)).start(); }}Copy the code
Verification results:
22:03:24.832 [Thread-0] the INFO com. Sachin. Threadlearn. Sync. SyncCode2. MyService - Thread: Thread -0To enter the synchronized code block22:03:24.832 [Thread-2] the INFO com. Sachin. Threadlearn. Sync. SyncCode2. MyService - Thread: Thread -2To enter the synchronous static method22:03:27.836 [Thread-2] the INFO com. Sachin. Threadlearn. Sync. SyncCode2. MyService - Thread: Thread -2, leaving the synchronous static method22:03:27.836 [Thread-0] the INFO com. Sachin. Threadlearn. Sync. SyncCode2. MyService - Thread: Thread -0, leaving the synchronized code block22:03:27.837 [Thread-1] the INFO com. Sachin. Threadlearn. Sync. SyncCode2. MyService - Thread: Thread -1To enter the synchronization method22:03:30.838 [Thread-1] the INFO com. Sachin. Threadlearn. Sync. SyncCode2. MyService - Thread: Thread -1, leaving the synchronization methodCopy the code
Synchronized (this) locks the current object, just as it does when modifying normal methods; Synchronized (myservice.class), the class object that is locked. It is also possible to lock any single object synchronized (object), in which case several threads can acquire the lock separately because each thread has a different lock.
4. Other scenarios
Synchronized also appears in other scenarios of multithreading, such as communication between threads (wait/notification mechanisms), deadlocks, and so on. Here, let’s take a look at deadlocks. A deadlock is when different threads are waiting for a lock that is impossible to release, causing all tasks to fail. The following uses an example of waiting for each other to lock to see deadlock situations:
@Slf4j
@AllArgsConstructor
public class DeadThread implements Runnable {
private String username;
private Object lock1;
private Object lock2;
@Override
public void run(a) {
if (username.equals("a")) {
methodA();
} else{ methodB(); }}private void methodA(a) {
synchronized (lock1) {
try {
TimeUnit.SECONDS.sleep(3);
log.info("Thread: {}, lock1 acquired, waiting to acquire lock2", Thread.currentThread().getName());
synchronized (lock2) {
log.info("Thread: {}, has acquired lock1, and has acquired lock2", Thread.currentThread().getName()); }}catch(InterruptedException e) { e.printStackTrace(); }}}private void methodB(a) {
synchronized (lock2) {
try {
TimeUnit.SECONDS.sleep(3);
log.info("Thread: {}, lock2 acquired waiting to acquire lock1", Thread.currentThread().getName());
synchronized (lock1) {
log.info("Thread: {}, has acquired lock2, and has acquired lock1", Thread.currentThread().getName()); }}catch(InterruptedException e) { e.printStackTrace(); }}}}Copy the code
Test method:
public static void main(String[] args) {
Object lock1 = new Object();
Object lock2 = new Object();
new Thread(new DeadThread("a", lock1, lock2), "Thread a").start();
new Thread(new DeadThread("b", lock1, lock2), "Thread b").start();
}
Copy the code
Create two threads, one to acquire the lock1 lock, then attempt to acquire the lock2 lock; In addition, a line first obtains the lock2 lock, then, obtains the lock1 lock; Since both threads start at the same time and sleep for 3 seconds before or after acquiring the other thread, it is enough for the other thread to acquire the lock. In other words, both threads are waiting for the lock to be released by the other thread. Obviously, if they cannot acquire the lock of the other thread, the lock will not be released, resulting in a deadlock.