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:
- Class Lock
- Other functions of the Lock class
- Class Condition
- Other functions of the Condition class
- 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.