Java concurrent Lock

Introduction to Lock

The Lock lock mechanism is a new lock mechanism after JDK 5. Different from the built-in lock, the Lock lock must be explicitly declared, and the lock must be released at the appropriate location. Lock is an interface, which is implemented by three concrete: ReentrantLock, ReetrantReadWriteLock.ReadLock and ReetrantReadWriteLock.WriteLock, namely reentrant lock, read lock and write lock. The addition of the Lock mechanism is mainly due to some functional limitations of the built-in lock. For example, it is impossible to interrupt a thread that is waiting to acquire a lock, and it is impossible to wait indefinitely while waiting for a lock. The built-in lock must be released in the code block that releases the lock. Although it simplifies the use of the lock, it causes other threads waiting to acquire the lock to acquire the lock by blocking waiting, which means that the built-in lock is actually a blocking lock. . The new Lock lock mechanism is a non-blocking lock (this will be described in detail later).

First, let's look at the source code of the Lock interface:

public interface Lock {
    //无条件获取锁
    void lock();
    //获取可响应中断的锁
    //在获取锁的时候可响应中断,中断的时候会抛出中断异常
    void lockInterruptibly() throws InterruptedException;
    //轮询锁。如果不能获得锁,则采用轮询的方式不断尝试获得锁
    boolean tryLock();
    //定时锁。如果不能获得锁,则每隔unit的时间就会尝试重新获取锁
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    //释放获得锁
    void unlock();
    //获取绑定的Lock实例的条件变量。在等待某个条件变量满足的之
    //前,lock实例必须被当前线程持有。调用Condition的await方法
    //会自动释放当前线程持有的锁
    Condition newCondition();

The comments are written in great detail and will not be repeated here. It can be seen that the newly added responsive interrupt locks and fair locks of the Lock lock mechanism are not available in the built-in lock mechanism locks. The sample code using the Lock lock is as follows:

Lock lock = new ReentrantLock();
        lock.lock();
        try {
            //更新对象状态
            //如果有异常则捕获异常
            //必要时恢复不变性条件
            //如果由return语句必须放在这里
        }finally {
            lock.unlock();
        }

 

Comparison of ReentrantLock and synchronized implementation strategies

The previous article mentioned that synchronized uses a mutual exclusion lock mechanism. The biggest problem with this synchronization mechanism is that when multiple threads need to acquire a lock, they can only wait for the thread that has acquired the lock to automatically wait for the thread that has acquired the lock by blocking synchronization. Release the lock. This process involves thread blocking and thread wakeup, and this process needs to be completed when the operating system switches from user mode to kernel mode. Then the problem comes. When multiple threads compete for the same lock, it will cause frequent context switching of the CPU, which is very inefficient and has a large system overhead. This strategy is called pessimistic concurrency strategy and is also the concurrency strategy used by synchronized.

ReentrantLock uses a more advanced concurrency strategy. Since the blocking caused by mutual exclusion synchronization will affect the performance of the system, is there a way to achieve synchronization without blocking? Concurrency guru Doug Lea (also the author of Lock) proposed to acquire locks by spinning. Simply put, if there is no contention to obtain the lock, then the acquisition is successful; if there is contention for the lock, then use the failure compensation measure (jdk 5 to the current jdk 8 uses continuous attempts to re-acquire until the get success) resolve the contention conflict. Since the spin occurs inside the thread, there is no need to block other threads, that is, non-blocking synchronization is achieved. This strategy is also known as an optimistic concurrency strategy based on conflict detection, and is also the concurrency strategy used by ReentrantLock.

A brief summary of ReentrantLock and synchronized, the advanced nature of the former is reflected in the following points:

  1. Interrupt-responsive locks. When the thread waiting for the lock cannot get the lock for a long time, it can choose not to wait and deal with other things, while the synchronized mutex must block and wait and cannot be interrupted
  2. Fair locking can be achieved. The so-called fair lock means that when multiple threads wait for the lock, they must wait in line according to the time when the thread applies for the lock. The unfair lock guarantees this, and each thread has the opportunity to obtain the lock. The synchronized lock and the default lock used by ReentrantLock are both unfair locks, but ReentrantLock supports fair locks. A boolean variable specified as true in the constructor is a fair lock. In general, however, using unfair locks outperforms using fair locks
  3. Each synchronized can only support binding one condition variable. The condition variable here refers to the condition of thread execution waiting or notification, while ReentrantLock supports binding multiple condition variables. Multiple condition variables can be obtained by calling lock.newCondition(). However, how many condition variables to use depends on the situation.

 

How to choose between ReentrantLock and synchronized

Consider using ReentrantLock only when some built-in locks cannot meet some advanced functions. These advanced features include: timed, pollable, and interruptible lock acquisition operations, fair queues, and non-block structured locks. Otherwise, synchronized should be used in preference.

This passage is the advice of concurrency guru Brian Goetz. So, let's analyze why it is still recommended to use synchronized in the premise that ReentrantLock has so many advantages?

First of all, the built-in lock is familiar to the developer lock (of course, this reason is not convincing), and the advantage of the built-in lock is that it avoids the operation of manually releasing the lock. If you forget to finally call unlock when using ReentrantLock, it is equivalent to planting a time bomb and affecting the execution of other code (not convincing enough). Second, using the built-in lock dump thread information can help analyze which call frames acquired which locks, and can help detect and identify deadlocked threads. This is something ReentrantLock can't do (it's a little convincing). Finally, synchronized will continue to be optimized in the future. The current synchronized has been optimized in terms of self-adaptation, spin, lock elimination, lock coarsening, lightweight locks and biased locks, and its performance in thread blocking and thread wake-up has been improved. Not so big anymore. On the other hand, the performance of ReentrantLock may stop there, and the possibility of future optimization is very small (well, I admit it). This is mainly because synchronized is a built-in property of the JVM, and it is natural to perform synchronized optimization (hey, after all, he is a son).

Use interruptible locks

An example of the use of interruptible locks is as follows:

    ReentrantLock lock = new ReentrantLock();
    ...........
    lock.lockInterruptibly();//获取响应中断锁
    try {
        //更新对象的状态
        //捕获异常,必要时恢复到原来的不变性条件
        //如果有return语句必须放在这里,原因已经说过了
    }finally{
        lock.unlock();
        //锁必须在finally块中释放
    }

The following shows how to use interruptible locks through a specific example:

First let's look at the example of using synchronized synchronization and then trying to interrupt

package com.rhwayfun.concurrency.r0405;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * Created by rhwayfun on 16-4-5.
 */
public class SyncInterruptDemo {

    //锁对象
    private static Object lock = new Object();
    //日期格式器
    private static DateFormat format = new SimpleDateFormat("HH:mm:ss");

    /**
     * 写数据
     */
    public void write(){
        synchronized (lock){
            System.out.println(Thread.currentThread().getName() + ":start writing data at " + format.format(new Date()));
            long start = System.currentTimeMillis();
            for (;;){
                //写15秒的数据
                if (System.currentTimeMillis() - start > 1000 * 15){
                    break;
                }
            }
            //过了15秒才会运行到这里
            System.out.println(Thread.currentThread().getName() + ":finish writing data at " + format.format(new Date()));
        }
    }

    /**
     * 读数据
     */
    public void read(){
        synchronized (lock){
            System.out.println(Thread.currentThread().getName() + ":start reading data at "
                    + format.format(new Date()));
        }
    }

    /**
     * 执行写数据的线程
     */
    static class Writer implements Runnable{

        private SyncInterruptDemo syncInterruptDemo;

        public Writer(SyncInterruptDemo syncInterruptDemo) {
            this.syncInterruptDemo = syncInterruptDemo;
        }

        public void run() {
            syncInterruptDemo.write();
        }
    }

    /**
     * 执行读数据的线程
     */
    static class Reader implements Runnable{

        private SyncInterruptDemo syncInterruptDemo;

        public Reader(SyncInterruptDemo syncInterruptDemo) {
            this.syncInterruptDemo = syncInterruptDemo;
        }

        public void run() {
            syncInterruptDemo.read();
            System.out.println(Thread.currentThread().getName() + ":finish reading data at "
                    + format.format(new Date()));
        }
    }

    public static void main(String[] args) throws InterruptedException {

        SyncInterruptDemo syncInterruptDemo = new SyncInterruptDemo();

        Thread writer = new Thread(new Writer(syncInterruptDemo),"Writer");
        Thread reader = new Thread(new Reader(syncInterruptDemo),"Reader");

        writer.start();
        reader.start();

        //运行5秒,然后尝试中断读线程
        TimeUnit.SECONDS.sleep(5);
        System.out.println(reader.getName() +":I don't want to wait anymore at " + format.format(new Date()));
        //中断读的线程
        reader.interrupt();
    }
}

The results are as follows:

write picture description here

As can be seen from the results, I tried to interrupt the read thread after it ran for 5 seconds, but found no result, because the write thread needs to run for 15 seconds, and 10 seconds after sleep 5 seconds (the 5 seconds of sleep plus 10 is exactly 15 seconds of the write thread ) The interrupt information is displayed only by the reading thread, which means that the interrupt event of the main thread is responded to after the writing thread releases the lock, that is to say, it is not allowed to be interrupted during the running of the synchronized code block, which also verifies the above synchronization. Discuss.

Then we use ReentrantLock to see if the read thread can respond to the interrupt normally. According to the analysis, after the read thread runs for 5 seconds, when the main thread interrupts the read thread, the read thread should be able to respond to the interrupt normally, and then stop executing the operation of reading data. Let's look at the code:

package com.rhwayfun.concurrency.r0405;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by rhwayfun on 16-4-5.
 */
public class LockInterruptDemo {
    //锁对象
    private static Lock lock = new ReentrantLock();
    //日期格式器
    private static DateFormat format = new SimpleDateFormat("HH:mm:ss");

    /**
     * 写数据
     */
    public void write() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + ":start writing data at "
                    + format.format(new Date()));
            long start = System.currentTimeMillis();
            for (;;){
                if (System.currentTimeMillis() - start > 1000 * 15){
                    break;
                }
            }
            System.out.println(Thread.currentThread().getName() + ":finish writing data at "
                    + format.format(new Date()));
        }finally {
            lock.unlock();
        }
    }

    /**
     * 读数据
     */
    public void read() throws InterruptedException {
        lock.lockInterruptibly();
        try {
            System.out.println(Thread.currentThread().getName() + ":start reading data at "
                    + format.format(new Date()));
        }finally {
            lock.unlock();
        }
    }

    /**
     * 执行写数据的线程
     */
    static class Writer implements Runnable {

        private LockInterruptDemo lockInterruptDemo;

        public Writer(LockInterruptDemo lockInterruptDemo) {
            this.lockInterruptDemo = lockInterruptDemo;
        }

        public void run() {
            lockInterruptDemo.write();
        }
    }

    /**
     * 执行读数据的线程
     */
    static class Reader implements Runnable {

        private LockInterruptDemo lockInterruptDemo;

        public Reader(LockInterruptDemo lockInterruptDemo) {
            this.lockInterruptDemo = lockInterruptDemo;
        }

        public void run() {
            try {
                lockInterruptDemo.read();
                System.out.println(Thread.currentThread().getName() + ":finish reading data at "
                        + format.format(new Date()));
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + ": interrupt reading data at "
                        + format.format(new Date()));
            }
            System.out.println(Thread.currentThread().getName() + ":end reading data at "
                    + format.format(new Date()));
        }
    }

    public static void main(String[] args) throws InterruptedException {

        LockInterruptDemo lockInterruptDemo = new LockInterruptDemo();

        Thread writer = new Thread(new Writer(lockInterruptDemo), "Writer");
        Thread reader = new Thread(new Reader(lockInterruptDemo), "Reader");

        writer.start();
        reader.start();

        //运行5秒,然后尝试中断
        TimeUnit.SECONDS.sleep(5);
        System.out.println(reader.getName() + ":I don't want to wait anymore at " + format.format(new Date()));
        //中断读的线程
        reader.interrupt();
    }

}

The results are as follows:

write picture description here

Obviously, the read thread responds to our interrupt normally, because the read thread outputs the interrupt information. Even after the write thread finishes writing the data, the read thread does not output the information to end reading the data, which is expected. This also verifies the analysis of interruptible locks.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325062128&siteId=291194637