1. Basic use of Synchronized
Synchronized is one of the most commonly used methods to solve concurrency problems in Java, and it is also the simplest method. Synchronized has three main functions: (1) To ensure that threads are mutually exclusive in accessing synchronization code (2) To ensure that modifications to shared variables can be seen in a timely manner (3) To effectively solve the reordering problem. Grammatically speaking, Synchronized has three usages:
(1) Modify ordinary methods
(2) Modify static methods
(3) Modify code blocks
Next, I will illustrate these three usage methods through several example programs (for the convenience of comparison, the three pieces of code are basically the same except for the different usage methods of Synchronized).
1. Without synchronization:
Code segment one:
package com.paddx.test.concurrent; public class SynchronizedTest { public void method1(){ System.out.println("Method 1 start"); try { System.out.println("Method 1 execute"); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Method 1 end"); } public void method2(){ System.out.println("Method 2 start"); try { System.out.println("Method 2 execute"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Method 2 end"); } public static void main(String[] args) { final SynchronizedTest test = new SynchronizedTest(); new Thread(new Runnable() { @Override public void run() { test.method1(); } }).start(); new Thread(new Runnable() { @Override public void run() { test.method2(); } }).start(); } }
The execution results are as follows, thread 1 and thread 2 enter the execution state at the same time, and thread 2 executes faster than thread 1 is faster, so thread 2 is executed first. During this process, thread 1 and thread 2 are executed at the same time.
Method 1 start
Method 1 execute
Method 2 start
Method 2 execute
Method 2 end
Method 1 end
2. Synchronization of ordinary methods:
Code segment two:
package com.paddx.test.concurrent; public class SynchronizedTest { public synchronized void method1(){ System.out.println("Method 1 start"); try { System.out.println("Method 1 execute"); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Method 1 end"); } public synchronized void method2(){ System.out.println("Method 2 start"); try { System.out.println("Method 2 execute"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Method 2 end"); } public static void main(String[] args) { final SynchronizedTest test = new SynchronizedTest(); new Thread(new Runnable() { @Override public void run() { test.method1(); } }).start(); new Thread(new Runnable() { @Override public void run() { test.method2(); } }).start(); } }
The execution results are as follows. Compared with code segment one, it can be clearly seen that thread 2 needs to wait for thread 1’s method1 to complete execution. Start executing the method2 method.
Method 1 start
Method 1 execute
Method 1 end
Method 2 start
Method 2 execute
Method 2 end
3、 Static method (class) synchronization
Code segment three:
package com.paddx.test.concurrent; public class SynchronizedTest { public static synchronized void method1(){ System.out.println("Method 1 start"); try { System.out.println("Method 1 execute"); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Method 1 end"); } public static synchronized void method2(){ System.out.println("Method 2 start"); try { System.out.println("Method 2 execute"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Method 2 end"); } public static void main(String[] args) { final SynchronizedTest test = new SynchronizedTest(); final SynchronizedTest test2 = new SynchronizedTest(); new Thread(new Runnable() { @Override public void run() { test.method1(); } }).start(); new Thread(new Runnable() { @Override public void run() { test2.method2(); } }).start(); } }
The execution results are as follows. Synchronization of static methods is essentially synchronization of classes (static methods are essentially methods belonging to classes. instead of methods on the object), so even if test and test2 belong to different objects, they both belong to instances of the SynchronizedTest class, so method1 and method2 can only be executed sequentially, not concurrently.
Method 1 start
Method 1 execute
Method 1 end
Method 2 start
Method 2 execute
Method 2 end
4、 Code block synchronization
Code segment four:
package com.paddx.test.concurrent; public class SynchronizedTest { public void method1(){ System.out.println("Method 1 start"); try { synchronized (this) { System.out.println("Method 1 execute"); Thread.sleep(3000); } } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Method 1 end"); } public void method2(){ System.out.println("Method 2 start"); try { synchronized (this) { System.out.println("Method 2 execute"); Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Method 2 end"); } public static void main(String[] args) { final SynchronizedTest test = new SynchronizedTest(); new Thread(new Runnable() { @Override public void run() { test.method1(); } }).start(); new Thread(new Runnable() { @Override public void run() { test.method2(); } }).start(); } }
The execution results are as follows. Although thread 1 and thread 2 have entered the corresponding method to start execution, thread 2 needs to wait before entering the synchronization block. The execution of the synchronized block in thread 1 is completed.
Method 1 start
Method 1 execute
Method 2 start
Method 1 end
Method 2 execute
Method 2 end
II. Synchronized principle
If you still have questions about the above execution results, don’t worry. Let’s first understand the principle of Synchronized, and then the above problems will be clear at a glance. Let’s first decompile the following code to see how Synchronized synchronizes the code block:
package com.paddx.test.concurrent; public class SynchronizedDemo { public void method() { synchronized (this) { System.out.println("Method 1 start"); } } }
Decompilation result:
About this For the functions of the two instructions, we directly refer to the description in the JVM specification:
monitorenter:
Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows: • If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor. • If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count. • If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.
The approximate meaning of this paragraph is:
Each object has a monitor lock (monitor). When the monitor is occupied, it will be in a locked state. When the thread executes the monitorenter instruction, it tries to obtain ownership of the monitor. The process is as follows:
1. If the entry number of the monitor is 0, the thread enters the monitor and then enters If the number is set to 1, the thread is the owner of the monitor.
2. If the thread already occupies the monitor and just re-enters, the number of entries into the monitor will be increased by 1.
3. If other threads have already occupied the monitor, the thread will enter the blocking state. , until the entry number of the monitor is 0, and then try to obtain the ownership of the monitor again.
monitorexit: The owner of the monitor.
When the instruction is executed, the entry number of the monitor is decremented by 1. If the entry number is 0 after decrementing 1, then the thread exits the monitor and is no longer the owner of the monitor. Other threads blocked by this monitor can try to obtain ownership of this monitor.
Through these two paragraphs of description, we should be able to clearly see the implementation principle of Synchronized. The underlying semantics of Synchronized are completed through a monitor object. In fact, wait/notify and other methods also rely on the monitor object. This is why methods such as wait/notify can only be called in synchronized blocks or methods, otherwise a java.lang.IllegalMonitorStateException exception will be thrown.
Let’s take a look at the decompilation result of the synchronization method:
Source code:
package com.paddx.test.concurrent; public class SynchronizedMethod { public synchronized void method() { System.out.println("Hello World!"); } }
从反编译的结果来看,方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。
三、运行结果解释
有了对Synchronized原理的认识,再来看上面的程序就可以迎刃而解了。
1、代码段2结果:
虽然method1和method2是不同的方法,但是这两个方法都进行了同步,并且是通过同一个对象去调用的,所以调用之前都需要先去竞争同一个对象上的锁(monitor),也就只能互斥的获取到锁,因此,method1和method2只能顺序的执行。
2、代码段3结果:
虽然test和test2属于不同对象,但是test和test2属于同一个类的不同实例,由于method1和method2都属于静态同步方法,所以调用的时候需要获取同一个类上monitor(每个类只对应一个class对象),所以也只能顺序的执行。
3、代码段4结果:
对于代码块的同步实质上需要获取Synchronized关键字后面括号中对象的monitor,由于这段代码中括号的内容都是this,而method1和method2又是通过同一的对象去调用的,所以进入同步块之前需要去竞争同一个对象上的锁,因此只能顺序执行同步块。
四 总结
Synchronized是Java并发编程中最常用的用于保证线程安全的方式,其使用相对也比较简单。但是如果能够深入了解其原理,对监视器锁等底层知识有所了解,一方面可以帮助我们正确的使用Synchronized关键字,另一方面也能够帮助我们更好的理解并发编程机制,有助我们在不同的情况下选择更优的并发策略来完成任务。对平时遇到的各种并发问题,也能够从容的应对。
更多Java 并发编程学习笔记之Synchronized简介相关文章请关注PHP中文网!