Java Programming Practice Chapter 14 Notes

Chapter 14 Building Custom Synchronization Tools

a conditional queue

It enables a set of threads (called a collection of waiting threads) to somehow wait for a specific condition to become true.

Just as each java object can be used as a lock, each object can also be used as a condition queue, and the wait, notify and notifyAll methods in Object constitute the API of the internal condition queue.

wait: wait means waiting, calling wait will automatically release the lock and request the system to suspend the current thread, so that other threads can obtain the lock 
notify: issue a notification to unblock the condition, the JVM will wait more from this condition queue Each thread chooses one to wake up 
notifyAll: issue a notification, unblock the condition, the JVM will wake up all threads waiting on this condition queue 
Condition Predicate: the condition the thread is waiting for

There is a very important ternary relationship in conditional waiting: synchronized, wait and a conditional predicate. 
The condition variable is protected by a lock, the lock must be held before checking the condition predicate, and the object on which the wait and notifyAll methods are called must be the same object.

The difference between notify and notifyAll: 

In most cases, notifyAll should be preferred. If thread A is waiting for the conditional predicate PA on the conditional queue, and thread B is waiting for the conditional predicate PB on the same conditional queue, if thread C turns PB true and calls notify, the JVM will choose A to wake up from among many waiting threads , but A sees that PA is still false, so it continues to wait, but thread B could have started executing, but was not woken up.

A single notify instead of notifyAll can only be used if the following two conditions are met: 
1. All waiting threads are of the same type 
2. Single-in, single-out: each notification on a condition variable can only wake up at most one thread to execute 

If there are 10 threads waiting in the condition queue, calling notifyAll will wake up each thread, let them compete for the lock, and then most or all of them will go back to sleep, which means that each event that activates a single thread of execution , will bring a lot of context switching, and a lot of competing lock requests

Two explicit Condition objects

The built-in conditional queue has some flaws, each built-in lock can only have one associated conditional queue. If you want to write a concurrent object with multiple conditional predicates, you can use Lock and Condition.

A Condition is associated with a single Lock, and a Condition can be created by calling the Lock.newCondition() method. Each Lock can have any number of Condition objects. wait, notify, notifyAll have corresponding counterparts in Condition: await, signal, signalAll, and be sure to use the latter!

Bounded cache using explicit condition variables:

public class ConditionBoundedBuffer<T> {  
    private static final int BUFFER_SIZE = 2;  
    private final Lock lock = new ReentrantLock();  
    private final Condition notFull = lock.newCondition();  
    private final Condition notEmpty = lock.newCondition();  
    private final T[] items = (T[]) new Object[BUFFER_SIZE];  
    private int tail, head, count;  

    public void put(T x) throws InterruptedException {  
        lock.lock();  
        try {  
            while (count == items.length) {  
                notFull.await(); //As long as the queue is full, then notFull waits
            }  
            items[tail] = x;  
            if (++tail == items.length) {  
                tail = 0;  
            }  
            count++;  
            notEmpty.signal(); //The queue is not empty, then wake up notEmpty
        } finally {  
            lock.unlock();  
        }  
    }  

    public T take() throws InterruptedException {  
        lock.lock();  
        try {  
            while (count == 0) {  
                notEmpty.await();// As long as the queue is empty, then notEmpty waits
            }  
            T x = items[head];  
            items[head] = null;  
            if (++head == items.length) {  
                head = 0;  
            }  
            count--;  
            notFull.signal(); //The queue is not full, then wake up notEmpty
            return x;  
        } finally {  
            lock.unlock();  
        }  
    }  
}  

三 AbstractQueuedSynchronizer(AQS)

AQS is a framework for building locks and synchronizers. Many synchronizers can be constructed through AQS, such as ReentrantLock, Semaphore, CountDownLatch, etc.

In the container built by AQS, the most basic is the acquisition operation and the release operation. For CountDownLatch, acquisition means waiting until the latch reaches the end state, and for FutureTask, acquisition means waiting until the task has been completed.

AQS is responsible for synchronizing the state in the container class. It manages an integer state information, which can be set and obtained through getState, setState and compareAndSetState. For example, ReentrantLock uses it to indicate the number of times the thread has repeatedly acquired the lock, Semaphore uses it to indicate the number of remaining permits, and FutureTask uses it to indicate the status of the task (not yet started, running, completed, and canceled).

A binary latch implemented using AQS is given below:

/**
 * Binary latching using AQS
 * @author cream
 *
 */


public class OneShotLatch{
	private final Sync sync = new Sync();
	
	public void signal(){
		sync.releaseShared(0);
	}
	
	public void await() throws InterruptedException{
		sync.acquireSharedInterruptibly(0);
	}
	private class Sync extends AbstractQueuedSynchronizer{
		protected int tryAcquireShared(int ignored){
			//If the latch is open (state==1), then the operation succeeds, otherwise it fails
			return (getState()==1) ? 1 : -1;
		}
		protected boolean tryReleaseShared(int ignored){
			setState(1);//Open the lock
			return true;//Indicates that other threads can acquire the lock
		}
	}
}
  • public final boolean releaseShared(int arg)

Releases in shared mode. Implemented by unblocking one or more threads if tryReleaseShared(int) returns true.

Parameters:arg - the release argument. This value is conveyed to tryReleaseShared(int) but is otherwise uninterpreted and can represent anything you like.

Returns:the value returned from tryReleaseShared(int)

The AQS state is used to indicate the latched state: closed (0) or open (1). The signal method will call releaseShared, which will then call tryReleaseShared to unconditionally set the lock state to open. The principle of await method is similar.

Guess you like

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