Java Multithreading Foundation - Lock Class

Keywords: Java jvm github

As mentioned earlier, JVM provides synchronized keywords for synchronized access to variables and uses wait and notify for inter-thread communication. After jdk1.5, JAVA provided Lock classes to implement the same functions as synchronized, and also provided Condition s to display inter-thread communication.
Lock classes are functions provided by Java classes, and the rich api makes the synchronization of Lock classes more powerful than synchronized ones. All the code in this article is in Code for the Lock class example
This article mainly introduces the content:

  1. Class Lock
  2. Other functions of the Lock class
  3. Class Condition
  4. Other functions of the Condition class
  5. Read write lock

Class Lock

The Lock class is actually an interface, and when we instantiate it, we actually instantiate the class Lock lock = new ReentrantLock();. When synchronized, synchronized can modify methods or synchronize a block of code.
As mentioned earlier, it's better to set up object monitors for code that needs synchronization than to synchronize the whole method. The same is true of the Lock class, which uses lock.lock to lock, lock.lock to unlock, and lock.unlock to unlock. Place code that needs to be processed synchronously between the two.
Specific examples are as follows:

public class MyConditionService {

    private Lock lock = new ReentrantLock();
    public void testMethod(){
        lock.lock();
        for (int i = 0 ;i < 5;i++){
            System.out.println("ThreadName = " + Thread.currentThread().getName() + (" " + (i + 1)));
        }
        lock.unlock();
    }
}

The test code is as follows:

        MyConditionService service = new MyConditionService();
        new Thread(service::testMethod).start();
        new Thread(service::testMethod).start();
        new Thread(service::testMethod).start();
        new Thread(service::testMethod).start();
        new Thread(service::testMethod).start();

        Thread.sleep(1000 * 5);

The result is too long to release, can see my source code specifically. In short, each thread print 1-5 is synchronized, the order is not messy.
Through the following example, we can see that when Lock object is locked, it is also an object lock. Only threads of the persistent object monitor can execute synchronization code, while other threads can only wait for the thread to release the object monitor.

public class MyConditionMoreService {

    private Lock lock = new ReentrantLock();
    public void methodA(){
        try{
            lock.lock();
            System.out.println("methodA begin ThreadName=" + Thread.currentThread().getName() +
                    " time=" + System.currentTimeMillis());
            Thread.sleep(1000 * 5);

            System.out.println("methodA end ThreadName=" + Thread.currentThread().getName() +
                    " time=" + System.currentTimeMillis());
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void methodB(){
        try{
            lock.lock();
            System.out.println("methodB begin ThreadName=" + Thread.currentThread().getName() +
                    " time=" + System.currentTimeMillis());
            Thread.sleep(1000 * 5);

            System.out.println("methodB end ThreadName=" + Thread.currentThread().getName() +
                    " time=" + System.currentTimeMillis());
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

The test code is as follows:

 public void testMethod() throws Exception {
        MyConditionMoreService service = new MyConditionMoreService();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();

        ThreadA aa = new ThreadA(service);
        aa.setName("AA");
        aa.start();

        ThreadB b = new ThreadB(service);
        b.setName("B");
        b.start();

        ThreadB bb = new ThreadB(service);
        bb.setName("BB");
        bb.start();

        Thread.sleep(1000 * 30);
    }
    
public class ThreadA extends Thread{

    private MyConditionMoreService service;

    public ThreadA(MyConditionMoreService service){
        this.service = service;
    }

    @Override
    public void run() {
        service.methodA();
    }
}

public class ThreadB extends Thread{

    private MyConditionMoreService service;

    public ThreadB(MyConditionMoreService service){
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.methodB();
    }
}

The results are as follows:

methodA begin ThreadName=A time=1485590913520
methodA end ThreadName=A time=1485590918522
methodA begin ThreadName=AA time=1485590918522
methodA end ThreadName=AA time=1485590923525
methodB begin ThreadName=B time=1485590923525
methodB end ThreadName=B time=1485590928528
methodB begin ThreadName=BB time=1485590928529
methodB end ThreadName=BB time=1485590933533

You can see that Lock class locking is indeed an object lock.lock. lock for the same lock object is the thread that gets the object monitor to execute the synchronization code. All other threads have to wait.
In this case, both try-finally and release locks are used. The advantage is that in any case of abnormal occurrence, the release of the lock can be guaranteed.

Other functions of the Lock class

If the Lock class only has lock and unlock methods, it is too simple, and the Lock class provides a rich method of locking and judgment of the situation of locking. There are mainly

  • Implementing Lock Fairness
  • Gets the number of locks called by the current thread, that is, the number of locks locked by the current thread.
  • Get the number of threads waiting for locks
  • Query whether the specified thread waits to acquire this lock
  • Query whether there are threads waiting to get this lock
  • Query whether the current thread holds a lock
  • Determine whether a lock is held by a thread?
  • If interruption does not lock when locking, enter exception handling
  • Attempt to lock if the lock is not held by other threads

Implementing Fair Lock

In the instantiation of lock object, there are two methods to construct, one is parametric construction method, the other is to import a boolean variable construction method. When the input value is true, the lock is a fair lock. The default no-pass parameter is an unfair lock.

Fair Locks: Acquire locks in the order in which threads lock
Unfair Locks: Random Competition for Locks
In addition, JAVA also provides isFair() to determine whether a lock is a fair lock.

Gets the number of current thread locks

Java provides a getHoldCount() method to get the number of locks in the current thread. The number of locks is the number of times the current thread calls the lock method. Generally, a method only calls a lock method, but there may be other methods called in the synchronization code, which has synchronization code inside. Thus, getHoldCount() returns a value greater than 1.

The following method is used to determine the waiting locks

Get the number of threads waiting for locks

Java provides the getQueueLength() method to get the number of threads waiting for the lock to be released.

Query whether the specified thread waits to acquire this lock

Java provides hasQueuedThread(Thread thread) to query whether the Thread waits for the release of the lock object.

Query whether there are threads waiting to get this lock

Similarly, Java provides a simple way to determine whether threads are waiting for lock release, namely hasQueuedThreads().

The following method is used to determine the holding of locks

Query whether the current thread holds a lock

Java not only provides a way to determine whether a thread is waiting for a lock to be released, but also whether the current thread holds a lock, isHeldByCurrentThread(), to determine whether the current thread has this lock.

Determine whether a lock is held by a thread?

Similarly, Java provides a simple way to determine whether a lock is held by a thread, isLocked()?

The following methods are used to implement locking in a variety of ways

If interruption does not lock when locking, enter exception handling

The Lock class provides a variety of alternative locking methods. lockInterruptibly() can also implement locking, but when a thread is interrupted, the locking fails and the exception handling phase is performed. Usually this happens when the thread has been marked interrupted.

Attempt to lock if the lock is not held by other threads

Java provides a tryLock() method for attempting to lock, which is successful only if the lock is not held by other threads.

The Lock class is introduced above to synchronize the code, and the Condition class is introduced below to implement the wait/notify mechanism.

Class Condition

Conditions are classes provided by Java to implement wait/notification. Conditions also provide richer functions than wait/notify. Conditions are created by lock objects. But the same lock can create multiple Conditions objects, that is, multiple object monitors. The advantage is that wake-up threads can be specified. The notify wake-up thread is a random wake-up.
Next, let's look at an example that shows a simple wait / notification

public class ConditionWaitNotifyService {

    private Lock lock = new ReentrantLock();
    public Condition condition = lock.newCondition();


    public void await(){
        try{
            lock.lock();
            System.out.println("await The time is " + System.currentTimeMillis());
            condition.await();
            System.out.println("await Time to End" + System.currentTimeMillis());
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }


    public void signal(){
        try{
            lock.lock();
            System.out.println("sign The time is" + System.currentTimeMillis());
            condition.signal();
        }finally {
            lock.unlock();
        }
    }
}

The test code is as follows:

        ConditionWaitNotifyService service = new ConditionWaitNotifyService();
        new Thread(service::await).start();
        Thread.sleep(1000 * 3);
        service.signal();
        Thread.sleep(1000);

The results are as follows:

The await time is 14856 1010 7421
 sign time is 14856 10110423
 The end time of await is 1485610110423

The condition object is created by lock.newCondition(), and the condition.await() is used to make the thread wait, which means that the thread is blocked. Use condition.signal() to wake up the thread. The wake-up thread is blocked by calling await() method with the same conditon object. And like wait/notify, await() and signal () are executed in the synchronous code area.
In addition, it can be seen that the statement at the end of await is executed only after the notification is obtained, and the function of wait/notify is indeed realized. The following example shows the thread of wake-up formulation.

        ConditionAllService service = new ConditionAllService();
        Thread a = new Thread(service::awaitA);
        a.setName("A");
        a.start();

        Thread b = new Thread(service::awaitB);
        b.setName("B");
        b.start();

        Thread.sleep(1000 * 3);

        service.signAAll();

        Thread.sleep(1000 * 4);

The results are as follows:

StarawaitA time is 148511065974 ThreadName = A
 StarawaitB time 14856 11065975 ThreadName = B
 signAll takes 14856 11068979 ThreadName = main
 The end awaitA time is 14856 11068979 ThreadName = A

This result does show that the same condition object is used to implement waiting notification.
To simplify the waiting/notification mechanism, it is to wait for a condition, enter the waiting when the condition is not satisfied, and notify the waiting thread to start executing when the condition is satisfied. In order to achieve this function, the code part that needs to wait and the code part that needs to be notified must be placed in the same object monitor. Execution enables multiple blocked threads to execute code synchronously, and the waiting and notification threads to execute code synchronously. For wait/notify, the object monitor is combined with the wait condition, that is, synchronized (object) uses the object to call wait and notify. But for the Condition class, the object monitor is separated from the condition. The Lock class implements the object monitor. The condition object is responsible for the condition and calls await and signal.

Other functions of the Condition class

The wait class and the wait class provide a maximum waiting time, and the awaitUntil (Date deadline) automatically wakes up after reaching the specified time. But whether await or awaitUntil, when a thread interrupts, the blocked thread produces an interrupt exception. Java provides a way to await Uninterruptibly so that even when a thread interrupts, a blocked thread does not produce an interrupt exception.

Read write lock

In addition to providing locks for ReentrantLock, the Lock class also provides locks for ReentrantReadWriteLock. Read-write locks are divided into two locks, one is read locks and the other is write locks. Read lock and read lock are shared, read lock and write lock are mutually exclusive, and write lock and write lock are mutually exclusive.
Look at the following examples of read-and-read sharing:

public class ReadReadService {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    public void read(){
        try{
            try{
                lock.readLock().lock();
                System.out.println("Get read lock" + Thread.currentThread().getName() +
                " " + System.currentTimeMillis());
                Thread.sleep(1000 * 10);
            }finally {
                lock.readLock().unlock();
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

The code and results of the test are as follows:

        ReadReadService service = new ReadReadService();
        Thread a = new Thread(service::read);
        a.setName("A");

        Thread b = new Thread(service::read);
        b.setName("B");

        a.start();
        b.start();

The results are as follows:

Get the read lock A 1485614976979
 Obtain read lock B 1485614976981

Two threads execute synchronous code almost simultaneously.
The following example is an example of writing mutually exclusive

public class WriteWriteService {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void write(){
        try{
            try{
                lock.writeLock().lock();
                System.out.println("Get write lock" + Thread.currentThread().getName() +
                        " " +System.currentTimeMillis());
                Thread.sleep(1000 * 10);
            }finally {
                lock.writeLock().unlock();
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

The test code and results are as follows:

        WriteWriteService service = new WriteWriteService();
        Thread a = new Thread(service::write);
        a.setName("A");
        Thread b = new Thread(service::write);
        b.setName("B");

        a.start();
        b.start();
        Thread.sleep(1000 * 30);

The results are as follows:

Get Write Lock A 1485615316519
 Get Write Lock B 1485615326524

Synchronized code execution by two threads

Examples of read-write exclusion:

public class WriteReadService {

    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void read(){
        try{
            try{
                lock.readLock().lock();
                System.out.println("Get read lock" + Thread.currentThread().getName()
                        + " " + System.currentTimeMillis());
                Thread.sleep(1000 * 10);
            }finally {
                lock.readLock().unlock();
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    public void write(){
        try{
            try{
                lock.writeLock().lock();
                System.out.println("Get write lock" + Thread.currentThread().getName()
                        + " " + System.currentTimeMillis());
                Thread.sleep(1000 * 10);
            }finally {
                lock.writeLock().unlock();
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

}

The test code is as follows:

        WriteReadService service = new WriteReadService();
        Thread a = new Thread(service::write);
        a.setName("A");
        a.start();
        Thread.sleep(1000);

        Thread b = new Thread(service::read);
        b.setName("B");
        b.start();

        Thread.sleep(1000 * 30);

The results are as follows:

Get Write Lock A 1485615633790
 Obtain read lock B 1485615643792

The two threads also execute code synchronously between read and write.

summary

This paper introduces the new way of synchronizing code, Lock class, and Condition class, which implements the new waiting/notification mechanism. This article simply introduces their concepts and ways of using them. More about Conditions and Read-Write Locks will be posted on future blogs.

Posted by rajivgonsalves on Thu, 21 Mar 2019 19:12:53 -0700