The use of synchronized
The object is locked
public class T {
private Object o=new Object();
private int count=10;
public void m() {
synchronized(o) {
count--;
System.out.println(Thread.currentThread().getName()+"count="+count); }}}Copy the code
1. When a thread wants to execute this code, it must acquire the lock of O. When O is occupied by another thread, the thread must wait for another thread to release the lock of O before acquiring the lock of O.
2. Synchronized locks objects, not code blocks. In demo, locks object instances
3. Possible lock objects include: this, critical resource object, Class object.
4. About thread safety: adding the synchronized keyword may not achieve thread safety, depending on whether the locked object is unique.
5. Synchronized keyword modifiers common methods equivalent to synchronized(this)
Static methods lock
Public class T {private static int count=1; private static int count=1; public synchronized static voidm() {
count--;
System.out.println(Thread.currentThread().getName()+"count="+count); }}Copy the code
When you lock a static method, it locks a class object, and the class file synchronize is unique, so it is thread-safe in multiple threads to refer to a static method or a locked object as a class file.
The important thing to note here is that if a class object is locked, even if multiple instance objects are new, they will still belong to the same class and will still be locked, i.e. synchronization between threads is guaranteed.
Public class T {private static int count=1; private static int count=1; public synchronized static voidm() {
count--;
System.out.println(Thread.currentThread().getName()+"count="+count);
}
public static void main(String args[]) {
for(int i=0; i<5; i++) { T t=new T(); new Thread(()->t.m(),"Thread"+i).start(); }}}Copy the code
Output result:
Because sychronized is a static method, no matter how many objects are new, we lock the same class object, so it’s thread-safe.
If static is removed, it is not thread-safe.
Synchronous and asynchronous methods are called simultaneously
public class T {
public synchronized void m1(){
System.out.println("m1 start------");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("m1 end--------");
}
public void m2() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("m2-----"); } public static void main(String args[]) { T t=new T(); M1 lamda /*new Thread(new)Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
}
});
*/
new Thread(()->t.m1(),"t1").start();
new Thread(()->t.m2(),"t1").start(); }}Copy the code
Thread T1 first acquires the lock on the current object T and executes M1. Since M2 is asynchronous and does not need to acquire a lock to execute, T2 does not need to acquire a lock to execute m2 directly. You only need to apply for that lock if you use the synchronized method.
Reentrant lock
public class Test1 {
synchronized void m1() {
System.out.println(Thread.currentThread().getName()+" m1");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
m2();
}
synchronized void m2() {
System.out.println(Thread.currentThread().getName()+" m2");
}
public static void main(String[] args) {
Test1 t=new Test1();
// TODO Auto-generated method stub
new Thread(()->{
t.m1();
},"t1").start();
new Thread(()->{
t.m1();
},"t2").start();; }} output: T1 m1 T1 m2 T2 m1 t2 m2Copy the code
The so-called ReentrantLock refers to the unit of thread. When a thread acquires an object lock, the thread can acquire the lock on the object again, while other threads cannot. Synchronized and ReentrantLock are reentrant locks. The point of reentrant locks is to prevent deadlocks. This is done by associating each lock with a request count and a thread that owns it. When the count is 0, the lock is considered unoccupated. When a thread requests an unused lock, the JVM records the owner of the lock and sets the request counter to 1. If the same thread requests the lock again, the count increases; The counter is decremented each time the occupying thread exits the synchronized block. Until the counter reaches 0, the lock is released. A reentrant lock must lock the same object.
Reentrant locks for parent and child classes:
public class Father {
synchronized void m1() {
System.out.println(" father");
}
}
public class Son extends Father {
@Override
synchronized void m1() {
// TODO Auto-generated method stub
super.m1();
System.out.println(" sun"); } } public static void main(String[] args) { // TODO Auto-generated method stub Son s=new Son(); s.m1(); } output: father sunCopy the code
If a thread has a subclass object reference loggingWidget, then call loggingWidget. DoSomething method, will request a subclass object loggingWidget object lock; Because the doSomething method of the parent class called in the loggingWidget actually requests the object lock of the subclass object loggingWidget, synchronized is not a reentrant lock. The result is a deadlock on the parent doSomething method held by the subclass object. Because of the reentrant lock of synchronized, the current thread already holds the object lock of the subclass object loggingWidget and can execute the synchronization method unhindered if the object lock of the subclass object loggingWidget is requested later.
Reading dirty data
public class Account {
String name;
double balance=0;
public synchronized void set(String name,Double balance) {
this.name=name;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.balance=balance;
}
public double getBalance() {
return balance;
}
public static void main(String args[]) {
Account a= new Account();
new Thread(()->a.set("aaaa", 100.0)). The start (); System.out.println(a.getBalance()); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(a.getBalance()); }}Copy the code
Locking getBalance solves this problem.
In the e-commerce field, the dirty reads on business logic are not considered, but on data. For example, orders are not displayed in real time.
Do not use string constants as lock objects.
Because you’re locking objects. For example, if you use a library that locks “Hello”, and you lock “Hello” in your code, it locks the same object and is subject to deadlocks.
Aotomic
public class Test_11 {
AtomicInteger count = new AtomicInteger(0);
void m() {for(int i = 0; i < 10000; i++){
/*if(count.get() < 1000)*/
count.incrementAndGet();
}
}
public static void main(String[] args) {
final Test_11 t = new Test_11();
List<Thread> threads = new ArrayList<>();
for(int i = 0; i < 10; i++){
threads.add(new Thread(new Runnable() {
@Override
public void run() { t.m(); }})); }for(Thread thread : threads){
thread.start();
}
for(Thread thread : threads){ try { thread.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println(t.count.intValue()); }}Copy the code
AtoXXX’s method is atomic, but it’s more efficient than synchronized.
Atomic cannot guarantee that multiple method calls in succession will be Atomic.
Lock object change
Once a lock is locked, a temporary reference to the lock object is created that does not directly relate to the actual reference. * Modifying the lock object reference before the lock is released does not affect the execution of the synchronization code. */ public class Test1 { Object o = new Object(); voidm(){
System.out.println(Thread.currentThread().getName() + " start");
synchronized (o) {
while(true){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-" + o);
//o=new Object();
}
}
}
public static void main(String[] args) {
final Test1 t = new Test1();
new Thread(new Runnable() {
@Override
public void run() { t.m(); }},"thread1").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() { t.m(); }},"thread2"); thread2.start(); t.o = new Object(); }}Copy the code
After A thread A gets the lock with the lock of the object binding monitor will record the thread object, after the lock object change, namely real reference point to the object is changed, then the waiting threads will compete again this new object, but the original lock object is not GC, waited until after A lock is released, thread to GC.
If an exception occurs during program execution, the lock is released by default.
Therefore, be careful with exception handling during concurrent execution, or inconsistencies may occur.
For example, if multiple servlet threads access the same resource, if exception handling is not appropriate, throw an exception in the first thread, and other threads will enter the synchronized code area, potentially accessing the exception data.
Synchronized low-level implementation
Java object head
Synchronized lock objects are stored in Java object headers, and the JVM uses two characters to store object headers (three characters are allocated if the object is an array, and the extra character records the array length). Its main structure is composed of Mark Word and Class Metadata Address. Class Metadata Address Stores the Address of the Class to which the object belongs.
There are five types of MarkWord:
MarkWord:
Heavyweight lock (sychronized) :
The lock identifier bit is 10, where the pointer points to the starting address of the monitor object (also known as the pipe or monitor lock). Each object has a Monitor associated with it. Only threads that acquire the object’s monitor can execute a method or code block. Other threads that fail to acquire the object are BLOCKED and placed in a synchronized queue, where they enter a BLOCKED state.
Monitor
When we use synchronized to modify the method name, an ACC_SYNCHRONIZED identifier is generated on the method name after compilation to implement synchronization; When synchronized modifiers are used, monitorenter and Monitorexit bytecodes are generated before and after the block for synchronization after compilation.
No matter which method is used, it is essentially the acquisition of the monitor associated with the specified object. Only the thread of the monitor that acquired the object can execute the method or code block. Other threads that failed to acquire the object will be BLOCKED and put into the synchronization queue, and enter the BLOCKED state.
In order to solve the problem of thread safety, Java provides a synchronization mechanism and a mutex mechanism, which ensures that only one thread can access a shared resource within the same problem. The guarantee for this mechanism comes from the Monitor lock, which each object has its own.
Here’s an example:
We can think of a monitor as a building containing a special room that can only have one guest (thread) at a time. This room contains some data and some code.
If a customer wants to enter this special room, he first has to wait in line at an Entry Set. The scheduler will select queuing customers to enter the room based on some criteria, such as FIFO. If, for some reason, the customer is temporarily tied to something else (the thread is suspended), then the customer will be sent to a separate Wait Set, which allows the customer to re-enter that particular room at a later time. As mentioned above, there are three places in this building.
Anyway, the monitor is a device that monitors these threads as they enter a particular room. His obligation is to ensure that only one thread can access protected data and code (at a time).
Monitor the implementation of the
Data structure:
ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL;
_WaitSet = NULL;
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ;
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}Copy the code
Key attributes:
_owner: points to the thread holding the ObjectMonitor object
_WaitSet: stores the queue of threads in wait state
_EntryList: stores the queue of threads in the lockwaiting block state
_recursions: number of lock reentrant times
_count: records the number of times the thread obtains the lock
When multiple threads concurrently access the same synchronized code, the thread will enter the _EntryList first. When the thread acquires the lock flag, the _Owner in monitor records the thread and increments the counter in monitor (+1) to represent the lock. Other threads continue to block in _EntryList. If the executing thread calls the wait method, the counter in monitor performs an assignment of 0 and assigns the _Owner flag to null, indicating that the lock is abandoned, and the executing thread blocks in an instance such as _WaitSet. If the executing thread calls notify/notifyAll, the thread in _WaitSet is woken up and blocks in _EntryList waiting for the lock flag. If the synchronization code of the executing thread completes, the lock flag is also released, the _Owner flag in monitor is assigned null, and the counter is evaluated at 0.
Code block underlying implementation
monitorenter monitorexit
Method underlying implementation
Method-level synchronization is implicit, that is, controlled without bytecode instructions, and is implemented in method calls and return operations. The JVM can distinguish whether a method is synchronized from the ACC_SYNCHRONIZED access flag in the method_info Structure in the method constant pool. 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 will hold monitor (a term used in the virtual machine specification) before executing the method. Monitor is finally released when the method completes, either normally or abnormally. During method execution, the executing thread holds the Monitor, and no other thread can obtain the same monitor. If an exception is thrown during the execution of a synchronous method and cannot be handled within the method, the monitor held by the synchronous method is automatically released when the exception is thrown outside the synchronous method.
Wake-on-wait mechanism and synchronized
This article mainly refers to notify/notifyAll and WAIT methods. To use these three methods, one must be in a synchronized code block or synchronized method. Otherwise you will be thrown IllegalMonitorStateException is unusual, this is because the call before this several methods must take the current monitor the monitor the object, that is to say wait and notify/notifyAll method relies on the monitor object, in the previous analysis, We know that monitor exists in the Mark Word of the object header (where the monitor reference pointer is stored), and that the synchronized keyword retrieves monitor, This is why notify/notifyAll and wait methods must be called in a synchronized code block or synchronized method.
Reentrancy of synchronized
In terms of mutex design, when a thread attempts to manipulate a critical resource of an object lock held by another thread, it will block. However, when a thread requests the critical resource of an object lock held by itself again, this situation is a reentrant lock. The request will succeed, and the count of monitor will increase by one. In Java, synchronized is an atomic-based internal locking mechanism and can be reentrant. Therefore, when a thread calls a synchronized method, another synchronized method of the object is called inside its method body. That is, a thread obtains an object lock and then requests the object lock again. Yes, that’s the reentrancy of synchronized.
Reference:
Blog.csdn.net/javazejian/…