Lock
Starting from JDK5.0, java provides a more powerful thread synchronization mechanism-through the explicit definition of synchronization lock objects to achieve synchronization
1.Lock interface
public interface Lock
Lock
Implementations provide more extensive locking operations than can synchronized
be obtained methods and reports. They allow a more flexible structure, can have completely different characteristics, can support multiple related Condition
objects.
Lock is a tool that controls access to shared resources through multiple threads. Generally, a lock provides exclusive access to shared resources: only one thread can obtain the lock at a time and all access to the shared resources needs to obtain the lock first. However, some locks can allow concurrent access to shared resources, such as a ReadWriteLock
read lock.
The synchronized
method or report provides access to the use of implicit monitor locks related to each object, but the acquisition and release of all locks of the force occurs in a structured way: when multiple locks are acquired, they must be released in the reverse order, And all the locks must be released with the same lexical scope as they are acquired.
While the mechanism synchronized
methods and statements make it easier to monitor lock programs, and help to avoid many common programming errors with locks, sometimes it is necessary to use locks in a more flexible way. For example, some algorithms that traverse concurrently accessed data structures need to use "hand-in-hand" or "chain lock": you get the lock node A, then node B, then release A and obtain C, then release B and obtain D, etc. . The Lock
implementation of this interface enables the use of technologies that allow one lock to be acquired and issued in different scopes, and allows multiple locks to be acquired and issue any commands.
With this increased flexibility comes additional responsibilities. The block is not locked and the structure and synchronized
method of unlocking the lock are automatically released when the report is released. In most cases, the following idioms should be used:
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
When locking and unlocking are in different scopes, care must be taken to ensure that all the code is executed, and the execution of the holding lock is subject to the final attempt or attempt to seize to ensure that the lock is released.
Lock
The implementation provides additional functions to provide a non-blocking synchronized
method of trying to acquire a lock and the use of statements ( tryLock()
), an attempt to acquire a lock can be interrupted ( lockInterruptibly()
, and an attempt to acquire a lock, timeout ( tryLock(long, TimeUnit)
).
A Lock
class can also provide behavior and semantics, which are completely different from the implicit monitor lock, such as guarantee order, non-reentrant usage, or deadlock detection. If an implementation provides such specialized semantics, then the implementation of these semantics must record these semantics.
Note that Lock
instances are just ordinary objects and can also be used as targets in synchronized
declarations. Obtaining an Lock
instance monitoring lock does not matter to any specified method of calling the instance lock()
. This is a suggestion, in order to avoid confusion, you do not use Lock
examples like this , except in your own implementation.
Unless otherwise specified, passing any parameter null
value will result in a NullPointerException
throw.
Memory synchronization
All Lock
implementations must implement the same memory synchronization semantics provided by built-in monitoring locks, such as The Java Language Specification (17.4 Memory Model) :
- A successful
lock
operation has the same memory synchronization effect as a successful locking action. - A successful
unlock
operation has the same memory synchronization effect as a successful unlocking operation.
Successfully lock and unlock operations, and can lock/unlock operations without any memory synchronization effects.
2. Problem introduction
Insecurity between threads:
Take a ticket as an example:
public class TestLock {
public static void main(String[] args) {
TestLock2 lock1 = new TestLock2();
// TestLock2 lock2 = new TestLock2();
// TestLock2 lock3 = new TestLock2();
//多个线程需要操作同一个对象
new Thread(lock1).start();
new Thread(lock1).start();
new Thread(lock1).start();
}
}
class TestLock2 implements Runnable{
int ticketNumber = 10;
public void run() {
while (true){
if (ticketNumber>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("获得票:"+(ticketNumber--));
}else {
break;
}
}
}
}
Insecure (resource access violation)
The previous solution was handled by synchronized (Object):
public class TestLock {
public static void main(String[] args) {
TestLock2 lock1 = new TestLock2();
// TestLock2 lock2 = new TestLock2();
// TestLock2 lock3 = new TestLock2();
//多个线程需要操作同一个对象
new Thread(lock1).start();
new Thread(lock1).start();
new Thread(lock1).start();
}
}
class TestLock2 implements Runnable{
int ticketNumber = 10;
public synchronized void run() {
while (true){
if (ticketNumber>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("获得票:"+(ticketNumber--));
}else {
break;
}
}
}
}
The result is correct:
3. Lock solution-ReentrantLock class
public class ReentrantLock
extends Object
implements Lock, Serializable
A reentrant mutex Lock
has the same basic behavior and semantics for monitoring locks using implicit synchronized
method and reporting access, but the extension function.
One ReentrantLock
is successfully locked by the thread at the end, but it has not been unlocked yet. A thread call lock
will return, successfully acquiring the lock, when the lock is not owned by another thread. If the current thread already owns the lock, the method will return immediately. This can be isHeldByCurrentThread()
checked using methods , and getHoldCount()
.
The constructor of this class accepts an optional fairness parameter. When set true
, contention, lock favors granted access to the longest waiting thread. Otherwise, this lock does not guarantee any specific access sequence. Programs that use fair locks accessed by many threads may show lower overall throughput (ie, slower than those using the default settings, and tend to be much slower), but there are smaller differences in time to acquire locks and lack of guaranteed starvation . Please note that the fairness of locks does not guarantee the fairness of thread scheduling. Therefore, one of the many threads that use a fair lock may acquire it multiple times in a row, while the other active threads are not progressing instead of the lock currently held. Also note that the irregular tryLock()
method does not respect the fairness setting. If the lock is available, it will succeed even if other threads are waiting.
It is the recommended practice to always follow a call lock
with a try
block, the most typical is to build before/after etc:
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
In addition to implementing the Lock
interface, this class defines some methods public
and protected
methods for checking the status of the lock . Some of these methods are just useful instruments and monitoring.
This type of serialization behaves in the same way as a deserialized built-in lock: the lock is in an unlocked state, regardless of its state when serialized.
This lock supports a maximum of 2147483647 recursive locks in the same thread. Attempting to Error
exceed this limit will result from locking the method.
The concept of reentrant locks
Reentrant lock means that the thread can repeatedly lock the same lock without being blocked, which can avoid deadlock.
ReentrantLock就是可重入锁
Realization -the key is to put the unlock in the finally statement to prevent deadlock when an error occurs.
public class TestLock {
public static void main(String[] args) {
TestLock2 lock1 = new TestLock2();
// TestLock2 lock2 = new TestLock2();
// TestLock2 lock3 = new TestLock2();
//多个线程需要操作同一个对象
new Thread(lock1).start();
new Thread(lock1).start();
new Thread(lock1).start();
}
}
class TestLock2 implements Runnable{
int ticketNumber = 10;
//定义lock锁
private final ReentrantLock lock = new ReentrantLock();
public void run() {
while (true){
try {
lock.lock();//加锁
if (ticketNumber>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("获得票:"+(ticketNumber--));
}else {
break;
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();//释放锁
}
}
}
}
4. The difference between synchronized and lock
- lock is an explicit lock (manual release), synchronized is an implicit lock (automatic release out of scope)
- lock can only lock code blocks, synchronized can lock methods
- Using Lock locks, JVM will spend less time, better performance, and has better scalability (ReentrantLock reentrant lock)
- Use sequence Lock>Sync code block>Sync method