Java multithreading basics Lock class

  • 2020-06-01 09:53:28
  • OfStack

As mentioned earlier, JVM provides the synchronized keyword for synchronous access to variables and for inter-thread communication using wait and notify. After jdk1.5, JAVA provides the Lock class to implement the same functionality as synchronized1, and Condition to display inter-thread communication.

Lock class is the function provided by Java class. The rich api makes the synchronization of Lock class more powerful than that of synchronized class. All the code in this article is in the Lock class example code

This paper mainly introduces the following contents:

Lock class Other functions of the Lock class Condition class Other functions of the Condition class Read-write lock

Lock class

The Lock class is actually an interface, and when we instantiate it, we're actually instantiating the class Lock lock = new ReentrantLock(); . With synchronized, synchronized can modify methods or synchronize a block of code.

As mentioned earlier, it is better to set the object monitor for the code that needs to be synchronized than to decorate the entire method with synchronized. The same is true of the Lock class, which locks through the Lock object lock, with lock.lock, and releases the lock with lock.unlock. Put the code that needs to be synchronized 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 results are too long do not put out, specific can see my source code. In short, each thread of printing 1-5 is synchronous, the order is not out of order.

From the following example, it can be seen that when the Lock object is locked, it is also an object lock. Only the thread that continues the object monitor can execute the synchronization code, and the 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 the Lock class lock is indeed an object lock. lock.lock executes against the same lock object. lock.lock is the thread that gets the object monitor to execute the synchronization code and other threads have to wait.

In this example, both the lock and release locks are in try-finally. This has the advantage of ensuring the release of the lock in the event of any exception.

Other functions of the Lock class

If the Lock class had only lock and unlock methods, it would be too simple. The Lock class provides a wealth of locking methods and the ability to judge what is locked. There are mainly

Achieve lock fairness Gets the number of times the current thread has called lock, which is the number of locks the current thread has Gets the number of threads waiting for a lock Queries whether the specified thread is waiting to obtain this lock Query whether there is a thread waiting to get this lock Queries whether the current thread holds a lock Determines whether a lock is held by a thread If the interrupt is not locked, it will enter exception handling Try locking if the lock is not held by another thread

Implement fair locking

When instantiating a lock object, there are two constructors, one that is a no-argument constructor and one that passes in an boolean variable. When the incoming value is true, the lock is a fair lock. Default no-pass parameter is an unfair lock.

Fair locks: locks are acquired in the order in which threads are locked

Unfair lock: random competition to get a lock

In addition, JAVA also provides isFair() to determine whether a lock is a fair lock.

Gets the number of current thread locks

Java provides the getHoldCount() method to get the number of locks for the current thread. The number of locks is the number of times the current thread has called the lock method. One method calls only one lock method, but it is possible that another method is called in the synchronization code, and that method has synchronization code inside. Thus, getHoldCount() returns a value greater than 1.

The following method is used to determine the condition of the waiting lock

Gets the number of threads waiting for a lock

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

Queries whether the specified thread is waiting to obtain this lock

Java provides hasQueuedThread(Thread thread) to query whether the Thread is waiting for the lock object to be released.

Query whether there is a thread waiting to get this lock

Similarly, Java provides a simple way to determine if a thread is waiting for the lock to be released: hasQueuedThreads().

The following method is used to determine if a lock is held

Queries whether the current thread holds a lock

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

Determines whether a lock is held by a thread

Again, 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 the interrupt is not locked, it will enter exception handling

The Lock class provides a variety of options for locking, and lockInterruptibly() can also implement locking, but when the thread is interrupted, the locking fails and the exception handling phase occurs. This occurs when the thread is already marked with interrupted.

Try locking if the lock is not held by another thread

Java provides the tryLock() method to attempt locking, which is only successful if the lock is not held by another thread.

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

Condition class

Condition is a class that Java provides to implement wait/notification. The Condition class also provides more functionality than wait/notify. Condition objects are created by lock objects. But you can create multiple Condition objects with the same lock, creating multiple object monitors. The advantage is that you can specify the wake up thread. notify wakes up one thread at random.

Next, take a 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 for  " + System.currentTimeMillis());
   condition.await();
   System.out.println("await End time " + System.currentTimeMillis());
  }catch (Exception e){
   e.printStackTrace();
  }finally {
   lock.unlock();
  }
 }
 public void signal(){
  try{
   lock.lock();
   System.out.println("sign The time for " + 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:


await The time for  1485610107421
sign The time for 1485610110423
await End time 1485610110423

The condition object is created by lock.newCondition (), and condition.await () is used to make the thread wait, so that the thread enters the block. Use condition.signal () to implement the wake up thread. The thread that wakes up is blocking by calling the await() method with the same conditon object. And like wait/notify1, await () and signal () are also executed in the synchronized code area.

In addition, it can be seen that the end statement of await is not executed until after the notification is obtained, which indeed implements the function of wait/notify. The following example shows the awakening of the specified thread.


  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:


begin awaitA The time for  1485611065974ThreadName=A
begin awaitB The time for  1485611065975ThreadName=B
signAll The time for 1485611068979ThreadName=main
end awaitA The time for 1485611068979ThreadName=A

This result does show that waiting notifications are implemented with the same condition object.

For the waiting/notification mechanism, in terms of simplification, it means to wait for 1 condition. When the condition is not satisfied, it will enter the waiting, and when the condition is satisfied, it will notify the waiting thread to start execution. In order to do this, the parts of the code that need to be wait must be in the same object monitor as the parts of the code that need to be notified. Execution allows multiple blocked threads to execute code synchronously, as do threads waiting for notifications. For wait/notify, the object monitor is combined with the wait condition in the first instance, synchronized (object) USES this object to invoke wait and notify. But for the Condition class, the object monitor is separated from the condition, the Lock class implements the object monitor, and the condition object handles the condition, calling await and signal.

Other functions of the Condition class

The wait class and wait class provide a maximum wait time, and awaitUntil (Date deadline) will automatically wake up after reaching the specified time. However, whether await or awaitUntil, when a thread is interrupted, the blocking thread will generate an interrupt exception. Java provides an awaitUninterruptibly method so that even when a thread is interrupted, the blocking thread does not generate an interrupt exception.

Read-write lock

The Lock class provides the ReentrantReadWriteLock lock in addition to the ReentrantLock lock. Read and write locks are divided into two locks, one for read and one for write. Read and read locks are Shared, read and write locks are mutually exclusive, and write and write locks are mutually exclusive.

Take a look at the following example of reading a share:


  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);
0

The test code and results are 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);
1

The results are 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);
2

The two threads execute synchronized code almost simultaneously.

The following example is an example of writing mutual exclusion


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:


  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);
4

The results are 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);
5

The two threads execute the code synchronously

An example of read-write mutex:


  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);
6

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);
7

The results are as follows:


 Get write lock A 1485615633790
 Get read lock B 1485615643792

Code is also executed synchronously between reads and writes by two threads.

conclusion

This article describes the Lock class in a new way to synchronize code and the implementation of the Condition class in a new wait/notify mechanism. This article is just a brief introduction to their concepts and how to use them. More on Condition and read-write locks in a future blog post.


Related articles: