What are the locks in java
I couldn't answer this question after reading <java concurrent programming>, which shows that I don't understand enough about the concept of locks. So I looked through the contents of the book again and suddenly felt like I opened my forehead. It seems that the best way to learn is to learn with problems and solve them.
In Java, there are two main types of locks: internal lock synchronized and display lock java.util.concurrent.locks.Lock. But if you think about it carefully, it seems that the summary is not correct. It should be a series of locks implemented by Java built-in locks and concurrent.
Why does this say, because in java everything is an object, and java has a lock built into each object, which can also be called an object lock/internal lock. The relevant lock operation is completed through synchronized.
Because of the flaws in the implementation of synchronized and the complexity of concurrent scenarios, someone has developed an explicit lock, and these locks are derived from java.util.concurrent.locks.Locks. Of course, it has been built into JDK1.5 and later versions.
synchronized
First, let’s take a look at the synchronized that is used more frequently. It is also used in my daily work. Synchronized is used to provide a lock mechanism for a certain code block. It will implicitly have a lock in Java objects. This lock is called an intrinsic or monitor locks. The thread automatically acquires this lock before entering the block protected by synchronized, until the lock is automatically released after the code is completed (or may also be an exception). Built-in locks are mutually exclusive. A lock can only be held by one thread at the same time, which will also lead to multiple threads, and the threads behind the lock will be blocked after being held. This enables the thread safety of the code to ensure atomicity.
Re-enter
Since Java built-in locks are mutually exclusive and the subsequent threads will cause blockage, what happens if the thread holding the lock enters again when trying to obtain the lock? For example, one of the following situations:
public class BaseClass { public synchronized void do() { System.out.println("is base"); }}public class SonClass extends BaseClass { public synchronized void do() { System.out.println("is son"); super.do(); }}SonClass son = new SonClass();son.do();At this time, the do method of the derived class will first hold the lock once, and then enter the lock again and hold it when calling super.do(). If the lock is mutually exclusive, it should be deadlocked at this time.
But the result is not the case, because the internal lock has the characteristic of reentrant, that is, the lock implements a reentrant mechanism, reference count management. When thread 1 holds the object's lock a, the reference to lock a will be calculated by adding 1. Then when thread 1 acquires lock a again, thread 1 still holds lock a, then the calculation will add 1. Of course, every time you exit the synchronization block, it will be reduced by 1 until it is 0.
Some features of synchronized
How to modify the code
Modification method
public class BaseClass { public synchronized void do() { System.out.println("is base"); }}This means directly locking a method, and you need to obtain a lock when entering this method block.
Modify code blocks
public class BaseClass { private static Object lock = new Object(); public void do() { synchronized (lock) { System.out.println("is base"); } }}Here, the range of the lock is reduced to some code blocks in the method, which improves the flexibility of the lock. After all, the granularity control of the lock is also a key issue for the lock.
Type of object lock
I often see that some codes use synchronized in special terms, and look at the following code:
public class BaseClass { private static Object lock = new Object(); public void do() { synchronized (lock) { } } public synchronized void doVoid() { } public synchronized static void doStaticVoid() { } public static void doStaticVoid() { synchronized (BaseClass.class) { } } }There are four situations here: modifying the code block, modifying the method, modifying the static method, and modifying the class object of BaseClass. So what are the differences in these situations?
Modify code blocks
In this case, we create an object lock, using synchronized(lock) in the code, which means using the built-in lock of the object. In this case, the lock control is handed over to an object. Of course there is another way to do this:
public void do() { synchronized (this) { System.out.println("is base"); }}Using this means the lock of the current object. The key to the built-in lock is also mentioned here. I provide a lock to protect this code. No matter which thread comes, it will face the same lock.
Method for modifying objects
What is the situation with this direct modification? In fact, it is similar to modifying code blocks, except that this is the lock of the current object by default. This way, it is relatively simple and clear to write the code. As mentioned earlier, the difference between modifying code blocks is mainly the difference between controlling granularity.
Modify static methods
Is there anything different about static methods? It is indeed different. The lock acquired at this time is no longer this, and the class pointed to by this object is the class lock. Because class information in Java will be loaded into the method constant area, the global is unique. This actually provides a global lock.
Class object of the modified class
This situation is actually quite similar to when modifying static methods, but it is still the same reason. This method can provide more flexible control granularity.
summary
Through the analysis and understanding of these situations, we can actually see that the main core concept of built-in lock is to provide a piece of code with a lock that can be used for mutually exclusive, and plays a function similar to a switch.
Java also provides some implementations for built-in locks. The main feature is that Java is all objects, and each object has a lock, so you can choose which lock to use according to the situation.
java.util.concurrent.locks.Lock
I looked at synchronized earlier. In most cases, it is almost enough. However, the system is becoming more and more complex in concurrent programming, so there are always many scenarios where synchronized processing is more difficult. Or as stated in <java concurrent programming>, locks in concurrent are a complement to internal locks, providing more advanced features.
Simple analysis of java.util.concurrent.locks.Lock
This interface abstracts the main operation of the lock, and thus allows locks derived from Lock to have these basic characteristics: unconditional, cyclable, timeable, interruptable. Moreover, the locking and unlocking operations are performed explicitly. Here is its code:
public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; void unlock(); Condition newCondition();} ReentrantLock
ReentrantLock is a reentrant lock, even the name is so explicit. ReentrantLock provides similar semantics to synchronized, but ReentrantLock must be called explicitly, such as:
public class BaseClass { private Lock lock = new ReentrantLock(); public void do() { lock.lock(); try { //.. } finally { lock.unlock(); } }}This method is quite clear for code reading, but there is a problem, that is, if you forget to add try finally or forget to write lock.unlock(), it will cause the lock to not be released, which may lead to some deadlocks. There is no risk of synchronized.
trylock
ReentrantLock implements the Lock interface, so it naturally has its features, including trylock. trylock is to try to acquire the lock. If the lock has been occupied by other threads, it will immediately return false. If it is not, it should be occupied and return true, which means that the lock has been obtained.
Another trylock method contains parameters. The function of this method is to specify a time, which means that you keep trying to obtain the lock during this time, and give up if the time has not been obtained.
Because trylock does not always block and wait for locks, it can avoid the occurrence of deadlocks more.
lockInterruptibly
lockInterruptibly responds to interrupts when threads acquire locks. If an interrupt is detected, an interrupt exception is thrown by the upper layer code. In this case, an exit mechanism is provided for a round-robin lock. In order to better understand the interruptible lock operation, a demo was written to understand it.
package com.test;import java.util.Date;import java.util.concurrent.locks.ReentrantLock;public class TestLockInterruptibly { static ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { Thread thread1 = new Thread(new Runnable() { @Override public void run() { try { doPrint("thread 1 get lock."); do123(); doPrint("thread 1 end."); } catch (InterruptedException e) { doPrint("thread 1 is interrupted."); } } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { try { doPrint("thread 2 get lock."); do123(); doPrint("thread 2 end."); } catch (InterruptedException e) { doPrint("thread 2 is interrupted."); } } }); thread1.setName("thread1"); thread2.setName("thread2"); thread1.start(); try { Thread.sleep(100);//Wait for a while to make thread1 execute in front of thread2} catch (InterruptedException e) { e.printStackTrace(); } thread2.start(); } private static void do123() throws InterruptedException { lock.lockInterruptibly(); doPrint(Thread.currentThread().getName() + " is locked."); try { doPrint(Thread.currentThread().getName() + " doSoming1...."); Thread.sleep(5000);//Ware a few seconds to facilitate viewing the order of threads doPrint(Thread.currentThread().getName() + " doSoming2...."); doPrint(Thread.currentThread().getName() + " is finished."); } finally { lock.unlock(); } } private static void doPrint(String text) { System.out.println((new Date()).toLocaleString() + " : " + text); }}There are two threads in the above code. Thread1 starts earlier than thread2. In order to see the lock process, the locked code is sleeped for 5 seconds, so that you can feel the process of the first and second threads entering the lock acquisition process. The final result of the above code is as follows:
2016-9-28 15:12:56 : thread 1 get lock.
2016-9-28 15:12:56 : thread1 is locked.
2016-9-28 15:12:56 : thread1 doSoming1....
2016-9-28 15:12:56 : thread 2 get lock.
2016-9-28 15:13:01 : thread1 doSoming2....
2016-9-28 15:13:01 : thread1 is finished.
2016-9-28 15:13:01 : thread1 is unloaded.
2016-9-28 15:13:01 : thread2 is locked.
2016-9-28 15:13:01 : thread2 doSoming1....
2016-9-28 15:13:01 : thread 1 end.
2016-9-28 15:13:06 : thread2 doSoming2....
2016-9-28 15:13:06 : thread2 is finished.
2016-9-28 15:13:06 : thread2 is unloaded.
2016-9-28 15:13:06 : thread 2 end.
It can be seen that thread1 obtains the lock first, and thread2 will also get the lock later, but thread1 has occupied it at this time, so thread2 does not get the lock until thread1 releases the lock.
**This code shows that the thread behind lockInterruptibly to acquire the lock needs to wait for the previous lock to be released before obtaining the lock. **But there is no interruptible feature yet, so some code is added to this:
thread2.start();try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace();} //Interrupt thread2 in 1 second thread2.interrupt();After thread2 is started, call thread2's interrupt method. OK, run the code first and see the results:
2016-9-28 15:16:46 : thread 1 get lock.
2016-9-28 15:16:46 : thread1 is locked.
2016-9-28 15:16:46 : thread1 doSoming1....
2016-9-28 15:16:46 : thread 2 get lock.
2016-9-28 15:16:47 : thread 2 is interrupted. <--Respond directly to thread interrupt
2016-9-28 15:16:51 : thread1 doSoming2....
2016-9-28 15:16:51 : thread1 is finished.
2016-9-28 15:16:51 : thread1 is unloaded.
2016-9-28 15:16:51 : thread 1 end.
Compared with the previous code, it can be found that thread2 is waiting for thread1 to release the lock, but thread2 itself interrupts, and the code behind thread2 will not continue to be executed.
ReadWriteLock
As the name suggests, it is a read-write lock. This kind of read-write lock application scenario can be understood in this way. For example, a wave of data is mostly provided for reading, and there are only a relatively small number of write operations. If a mutex lock is used, it will lead to lock competition among threads. If everyone can read it when reading, lock a resource once it is to be written. Such changes solve this problem well, allowing the read operation to improve the read performance without affecting the write operation.
A resource can be accessed by multiple readers, or accessed by one writer, and both cannot be performed simultaneously.
This is the abstract interface for read and write locks, defining a read lock and a write lock.
public interface ReadWriteLock { /** * Returns the lock used for reading. * * @return the lock used for reading */ Lock readLock(); /** * Returns the lock used for writing. * * @return the lock used for writing */ Lock writeLock();}There is a ReentrantReadWriteLock implementation in JDK, which is a reentrant read-write lock. ReentrantReadWriteLock can be constructed into two types: fair or unfair. If not specified explicitly during construction, a non-fair lock will be created by default. In the non-fair lock mode, the order of thread access is uncertain, that is, it can be broken into; it can be downgraded from the writer to the reader, but the reader cannot be upgraded to the writer.
If it is fair lock mode, then the option is handed over to the thread with the longest waiting time. If a read thread obtains the lock and a write thread requests the write lock, then the read lock acquisition will no longer be received until the write operation is completed.
Simple code analysis actually maintains a sync lock in ReentrantReadWriteLock, but it looks semantically like a read lock and a write lock. Take a look at its constructor:
public ReentrantReadWriteLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); readerLock = new ReadLock(this); writerLock = new WriteLock(this);}//The constructor of read lock protected ReadLock(ReentrantReadWriteLock lock) { sync = lock.sync;}//The constructor of write lock protected WriteLock(ReentrantReadWriteLock lock) { sync = lock.sync;}You can see that the sync lock object of ReentrantReadWriteLock is actually referenced when constructed. And this Sync class is an internal class of ReentrantReadWriteLock. In short, read/write locks are all done through Sync. How does it collaborate on the relationship between the two?
//Locking method for reading lock public void lock() { sync.acquireShared(1);}//Locking method for writing lock public void lock() { sync.acquire(1);}The main difference is that the read lock obtains a shared lock, while the write lock acquires an exclusive lock. There is a point here that can be mentioned, that is, in order to ensure reentrantReadWriteLock, both shared locks and exclusive locks must support holding counts and reentrants. ReentrantLock is stored using state, and state can only store one shaping value. In order to be compatible with the problem of two locks, it is divided into the number of threads that hold the shared lock or the number of threads that hold the exclusive lock or the reentry count respectively.
other
I wrote a large article that I felt like it would take too long to write, and there are some more useful locks:
CountDownLatch
It is to set a counter that is held simultaneously. When the caller calls the await method of CountDownLatch, it will block if the current counter is not 0. Calling the release method of CountDownLatch can reduce the count until the caller who calls await will unblock.
Semaphone
Semaphore is a form of authorization and license, such as setting up 100 licenses, so that 100 threads can hold locks at the same time, and if this amount exceeds, it will return to failure.
Thank you for reading this article, I hope it can help you. Thank you for your support for this site!