In depth understanding of Java explicit locking

  • 2021-09-24 22:44:52
  • OfStack

Directory 1. Explicit locking 2. Common use of Lock api 3. Standard use of Lock 4. ReentrantLock (reentrant lock) 5. ReentrantReadWriteLock (read-write lock) 6. Condition

1. Explicit lock

What is an explicit lock?

A lock that you manually acquire and then release by yourself.

Why Lock (display lock) with synchronized (built-in lock)?

If the lock function is implemented by using synchronized keyword, the lock will be acquired implicitly by using synchronized keyword, but it will solidify the acquisition and release of the lock, that is, acquire first and then release.

Different from the built-in locking mechanism, Lock provides an unconditional, polling, timed and interruptible lock acquisition operation, and all locking and unlocking methods are explicit.

2. Common api for Lock

方法名称 描述
void lock() 获取锁
void lockInterruptibly() throws InterruptedException 可中断的获取锁,和lock()方法的不同之处在于该方法会响应中断,即在锁的获取中可以中断当前线程
boolean tryLock() 尝试非阻塞的获取锁,调用该方法后立刻返回,如果能够获取则返回true,否则返回false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException 超时获取锁,当前线程会在以下3种情况下会返回:
1. 当前线程在超时时间内获得了锁
2.当前线程在超市时间内被中断
3. 超时时间结束,返回false
void unlock(); 释放锁

3. Standard usage of Lock


lock.lock();
try {
    //  Business logic 
} finally {
    lock.unlock();
}
The lock is released in the finally block to ensure that after the lock is acquired, it can be finally released. Do not write the process of acquiring the lock in the try block, because if an exception occurs while acquiring the lock (the implementation of the custom lock), the exception will also cause the lock to be released for no reason.

4. ReentrantLock (reentrant lock)

The common implementation class for the Lock interface is ReentrantLock.

Sample code: The main thread is 100,000 times subtracted and the sub-thread is 100,000 times added.


public class ReentrantLockTest {

    private Lock lock = new ReentrantLock();
    private int count = 0;

    public int getCount() {
        return count;
    }

    private static class ReentrantLockThread extends Thread {
        private ReentrantLockTest reentrantLockTest;

        public ReentrantLockThread(ReentrantLockTest reentrantLockTest) {
            this.reentrantLockTest = reentrantLockTest;
        }

        @Override
        public void run() {
            for (int i = 0; i < 100000; i++) {
                reentrantLockTest.incr();
            }
            System.out.println(Thread.currentThread().getName() + " end, count =  " + reentrantLockTest.getCount());
        }
    }

    private void incr() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    private void decr() {
        lock.lock();
        try {
            count--;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ReentrantLockTest reentrantLockTest = new ReentrantLockTest();
        new ReentrantLockThread(reentrantLockTest).start();

        for (int i = 0; i < 100000; i++) {
            //  Decrease 100000
            reentrantLockTest.decr();
        }
        System.out.println(Thread.currentThread().getName() + " count =  " + reentrantLockTest.getCount());
    }
}

1. Lock reentrancy

Simply put, it is: "The same thread can continue to apply for the right to use the lock for many times for the lock that has been acquired". The synchronized keyword implicitly supports re-entry, such as a recursive method modified by synchronized. When the method is executed, the executing thread can still acquire the lock several times after acquiring the lock

Similarly, when ReentrantLock calls the lock () method, the thread that has acquired the lock can call the lock () method again to acquire the lock without being blocked

2. Fair and Unfair Locks

If, in time, Request 1, which acquires the lock first, is satisfied first, then the lock is fair; Otherwise, it is unfair. Fair lock acquisition means that the thread with the longest waiting time has the highest priority to acquire the lock, which can also be said that lock acquisition is sequential. ReentrantLock provides a constructor that controls whether a lock is fair (the default is unfair). In fact, fair locking mechanisms are often less efficient than unfair ones. One reason why unfair locks perform better than fair locks in highly competitive situations is that there is a significant delay between the resumption of a suspended thread and the actual start of the thread. Suppose thread A holds a lock and thread B requests the lock. Since the lock is already held by thread A, B will be suspended. When A releases the lock, B will wake up, so it will try to acquire the lock again. At the same time, if C also requests this lock, C will probably acquire, use and release this lock before B is fully awakened. This situation is a "win-win" situation: B has not delayed the acquisition of the lock, C has acquired the lock earlier, completed its own task, and then released the lock, and the throughput has also been improved.

5. ReentrantReadWriteLock (read-write lock)

ReentrantReadWriteLock is the implementation class of ReadWriteLock.

The locks mentioned earlier are basically exclusive locks, which only allow one thread to access at the same time, while read-write locks can allow multiple read threads to access at the same time, but when the write threads access, all read threads and other write threads are blocked. The read-write lock maintains one pair of locks, one read lock and one write lock. By separating the read lock from the write lock, the concurrency is greatly improved compared with the general exclusive lock.

Read locks do not exclude read locks, but reject write locks. Write locks exclude both read locks and write locks.


private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock getLock = lock.readLock(); //  Read lock 
private final Lock setLock = lock.writeLock(); //  Write lock 

As for locking and unlocking, it is caused by using mode 1 with ReentrantLock.

6. Condition

Any Java object has a set of monitor methods (defined on java. lang. Object), mainly including wait (), wait (long timeout), notify () and notifyAll () methods, which can cooperate with synchronized synchronization keywords to implement wait/notification mode. The Condition interface also provides a monitor method similar to Object, which can be used in conjunction with Lock to implement wait/notification mode.

Commonly used api

方法名称 描述
void await() throws InterruptedException 使当前线程进入等待状态直到被通知(signal)或中断
void signal() 唤醒1个等待的线程
void signalAll() 唤醒所有等待的线程

Sample code, the main thread calls the method to wake up two sub-threads.


public class ConditionTest {

    private volatile boolean flag = false;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    private void task1() {
        lock.lock();
        try {
            try {
                System.out.println(Thread.currentThread().getName() + "  Waiting ");
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "  Wait for the end ");
            System.out.println(" Send Mail ");
        } finally {
            lock.unlock();
        }
    }

    private void task2() {
        lock.lock();
        try {
            try {
                System.out.println(Thread.currentThread().getName() + "  Waiting ");
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "  Wait for the end ");
            System.out.println(" Send a text message ");
        } finally {
            lock.unlock();
        }
    }

    private void updateFlag() {
        lock.lock();
        try {
            this.flag = true;
            this.condition.signalAll();
        }finally {
            lock.unlock();
        }
    }

    private static class ConditionThread1 extends Thread {
        private ConditionTest conditionTest;
        public ConditionThread1(ConditionTest conditionTest) {
            this.conditionTest = conditionTest;
        }

        @Override
        public void run() {
            if (!conditionTest.flag) {
                conditionTest.task1();
            }
        }
    }

    private static class ConditionThread2 extends Thread {
        private ConditionTest conditionTest;
        public ConditionThread2(ConditionTest conditionTest) {
            this.conditionTest = conditionTest;
        }

        @Override
        public void run() {
            if (!conditionTest.flag) {
                conditionTest.task2();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ConditionTest conditionTest = new ConditionTest();
        new ConditionThread1(conditionTest).start();
        new ConditionThread2(conditionTest).start();
        Thread.sleep(1000);
        System.out.println("flag  Change. . . ");
        conditionTest.updateFlag();
    }
}

Related articles: