Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.
This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.
In Java concurrent programming, synchronized and volatile are two very important keywords that can be used to control mutual exclusion and visibility in concurrency. In this article, we will take a look at how synchronized should be used in concurrent environments and how it can ensure mutual exclusion and visibility.
Before we begin, let’s take a look at the concepts of mutual exclusion and visibility:
- Mutual exclusion: The coordination mechanism in multithreading is implemented by allowing only one thread to hold an object lock at a time, so that only one thread can access the code block (compound operation) to be synchronized at a time. Mutual exclusion is also often referred to as atomicity of operations.
- Visibility: You must ensure that changes made to a shared variable before the lock is released are visible to the other thread that subsequently acquired the lock (that is, the value of the latest shared variable should be obtained at the time the lock was acquired), otherwise another thread may continue operating on a copy of the local cache and cause inconsistencies.
The synchronized keyword is used to control thread synchronization. In a multi-threaded environment, using synchronized can prevent code from being executed by multiple threads at the same time.
1. Synchronize non-static methods
The modified method is called a synchronous method, where the lock is an instance object of the current class.
A. Multiple threads accessing the same objectsynchronized
Methods:
public class SynchronizedDemo1 {
public synchronized void access(a) {
try {
System.out.println(Thread.currentThread().getName()+" start");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+" end");
}catch(Exception e){ e.printStackTrace(); }}public static void main(String[] args) {
SynchronizedDemo1 demo01=new SynchronizedDemo1();
for(int i=0; i<5; i++){newThread(demo01::access).start(); }}}Copy the code
Running results:
As you can see, when multiple threads operate on a synchronized method on the same object, only one thread can grab the lock. After one thread acquires the lock on an object, other threads cannot acquire the lock and must wait for the lock to be released before they can access the synchronized method.
B. Different synchronized methods of multiple threads accessing the same object:
public class SynchronizedDemo2 {
public synchronized void access1(a) {
try {
System.out.println(Thread.currentThread().getName()+" in access1 start");
TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName()+" in access1 end");
} catch(Exception e) { e.printStackTrace(); }}public synchronized void access2(a) {
try {
System.out.println(Thread.currentThread().getName()+" in access1 start");
TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName()+" in access1 end");
} catch(Exception e) { e.printStackTrace(); }}public static void main(String[] args) {
SynchronizedDemo2 test = new SynchronizedDemo2();
new Thread(test::access1).start();
newThread(test::access2).start(); }}Copy the code
Running results:
This confirms that when a thread accesses any method modified by synchronized, if the current object is locked by another thread, it needs to wait for the other thread to release the current object lock first.
C, multiple different object threads access synchronized method:
public class SynchronizedDemo3 {
public synchronized void access1(a) {
try {
System.out.println(Thread.currentThread().getName()+" start");
TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName()+" end");
} catch(Exception e) { e.printStackTrace(); }}public static void main(String[] args) {
final SynchronizedDemo3 test1 = new SynchronizedDemo3();
final SynchronizedDemo3 test2 = new SynchronizedDemo3();
new Thread(test1::access1).start();
newThread(test2::access1).start(); }}Copy the code
Running results:
You can see that both threads start executing at the same time, and since they belong to different objects and lock instance objects generated by the class, they acquire different locks. Thus, threads generated by different objects can access synchronized methods at the same time.
2. Synchronous static methods
Static methods belong to classes, not objects, so synchronized static methods lock the class object of that class.
public class SynchronizedDemo4 {
public synchronized static void access(a) {
try {
System.out.println(Thread.currentThread().getName()+" start");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+" end");
}catch(Exception e){ e.printStackTrace(); }}public static void main(String[] args) {
for(int i=0; i<5; i++){newThread(SynchronizedDemo4::access).start(); }}}Copy the code
Running results:
Analysis shows that when synchronized modifies static methods, mutual exclusion also occurs between threads, and while one thread accesses a synchronized method, the other threads must wait. Because synchronized modifies static methods, the lock is a class object, not an instance object of the class.
3. Synchronize code blocks
The code block being decorated is called a synchronous code block, and its scope is the code enclosed in braces, in which case the lock is the object within the braces.
So why use synchronized code blocks? When a method is long and only a small part of the code needs to be synchronized, synchronizing the entire method can cause too long a wait. Instead of synchronizing the entire method, we can use synchronized blocks to surround the code that needs to be synchronized.
According to different lock objects, they can be divided into the following two categories:
A. Lock objects as locks:
Using an instance object as a lock, a thread must hold the object lock whenever it needs to enter a synchronized block of code, while subsequent threads must wait for the object to be released.
// Take this as an example
public void accessResources(a) {
synchronized (this) {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + " is running");
} catch(Exception e) { e.printStackTrace(); }}}Copy the code
Here, because this refers to the current object, it cannot be used on static methods.
Class lock (); class lock ();
public void accessResources(a) {
synchronized (SynchroDemo5.class) {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + " is running");
} catch(Exception e) { e.printStackTrace(); }}}public static void main(String[] args) {
final SynchroDemo5 demo5 = new SynchroDemo5();
for (int i = 0; i < 5; i++) {
newThread(demo5::accessResources).start(); }}Copy the code
At this point, all objects that have the class object use the same lock.
When there is no explicit Object to use as a lock and you just want to synchronize a piece of code, you can create a special Object to use as a lock, such as an Object Object.
private final Object MUTEX =new Object();
public void methodName(a){
Synchronized(MUTEX ){
//TODO}}Copy the code
So what is the underlying implementation of synchronized? We are divided into synchronized code blocks and synchronized methods.
The principle of
Decompile class files generated using classes that use synchronized code blocks:
Monitorenter and MonitoreXit are used to control access to the synchronization code.
Monitorenter:
Each object has a monitor lock. The monitor is locked when it is occupied, and the thread attempts to acquire ownership of the Monitor when it executes the Monitorenter instruction as follows:
- if
monitor
If the number of entries is 0, the thread entersmonitor
, and then set the number of entries to 1, the thread ismonitor
All of the - If the thread already owns it
monitor
, just re-enter, then entermonitor
Plus 1 - If another thread is already occupied
monitor
, the thread enters the blocking state untilmonitor
Is 0, then try again to obtainmonitor
Ownership of.
monitorexit:
The thread executing Monitorexit must be a monitor holder. When the instruction is executed, the number of entries in the monitor decreases by 1. If the number of entries decreases by 1, the thread exits the Monitor and is no longer the owner of the monitor. Other threads blocked by the Monitor can attempt to acquire ownership of the monitor.
Decompile class files generated by classes that use synchronous methods:
Method synchronization is not done by monitorenter and Monitorexit, which have an ACC_SYNCHRONIZED identifier in their constant pool compared to normal methods. The JVM synchronizes methods based on this identifier: When a method is invoked, the calling instruction checks whether the ACC_SYNCHRONIZED access flag of the method is set. If so, the thread of execution obtains monitor, executes the method body after the method is successfully acquired, and releases Monitor after the method is executed. During method execution, the same Monitor object is no longer available to any other thread. There is essentially no difference, except that method synchronization is done implicitly, without bytecode.
Well, that’s all for this article, and we’ll look at volatile in the next one.
The last
If you think it is helpful, you can like it and forward it. Thank you very much
Public number agriculture ginseng, add a friend, do a thumbs-up friend ah
For more on the principles of synchronized, see this article: Upgrading Synchronized Locks again