Build custom synchronization tools in Java concurrent programming

  • 2020-04-01 03:49:11
  • OfStack

When the Java class library does not provide a suitable synchronization tool, you need to build a custom synchronization tool.

A structure that can block state-dependent operations


acquir lock on object state;//Request for lock
while(precondition does not hold){//The preconditions are not met
   release lock;//First release the lock
   wait until precondition might hold;//Wait for the prerequisites to be met
   optionlly fail if interrupted or timeout expires;//Execution failed due to an interrupt or timeout
   reacquire lock;//Retry to get the lock
}
perform action//Perform < br / >    release lock;//Release lock

Example of a bounded cache implementation base class


public class BaseBoundBuffer<V> {
private final V[] buf;
private int tail;
private int head;
private int count;
@SuppressWarnings("unchecked")
public BaseBoundBuffer(int capacity) {
buf = (V[]) new Object[capacity];
}
public synchronized void doPut(V v) {
buf[tail] = v;
if (++tail == buf.length)
tail = 0;
count++;
}
public synchronized V doTake() {
V v = buf[head]; if (++head == buf.length)
head = 0;
count--;
return v;
}
public final synchronized boolean isFull() {
return count == buf.length;
}
public final synchronized boolean isEmpty() {
return count == 0;
}
}

Blocking implementation: throws an exception to the caller


public synchronized void put1(V v)  throws Exception{
if(isFull())
throw new Exception("full error");
doPut(v);
}

Analysis: exceptions should be applied to the occurrence of exceptions, throwing exceptions here is not appropriate; The need for a caller is to handle the case where the prerequisite fails and does not address the underlying problem.
Blocking method two: through polling and sleep

public void put2(V v) throws InterruptedException {
while (true) {//Polling < br / > synchronized (this) {
if (!isFull()) {
doPut(v);
return;    
}
}
Thread.sleep(SLEEP_TIME);//Dormancy < br / > }
}

Analysis: it's hard to weigh the SLEEP_TIME setting. If the Settings are too small, the CPU may be polling many times, and the higher the CPU consumption; If you set it too high, the less responsive it is.

Blocking method three: conditional queue

The elements in the condition queue are threads waiting for related conditions. Each Java Object can be used as a lock, each Object can be used as a condition queue, and the wait, notify, and notifyAll methods in the Object constitute the API for the internal condition queue. Object.wait automatically releases the lock and requests the operating system to suspend the current thread so that other threads can acquire the lock and modify the state of the Object. Object.notify and object.notifyall wake up a waiting thread, pick a thread from the condition queue to wake up and try to reacquire the lock.


public synchronized void put3(V v) throws InterruptedException {
while(isFull())
wait();
doput(v);
notifyAll();
}

Analysis: good response, easy to use.

Use conditional queues ​
1. Conditional predicates

1). Definition: a conditional predicate is a prerequisite that makes an operation a state-dependent operation. A conditional predicate is an expression composed of various state variables in a class. For example, the conditional predicate for the put method is "cache is not empty."
2). Relationships: there is an important ternary relationship in conditional waits, including locking, wait methods, and a conditional predicate. There are multiple state variables in the condition predicate, and each state variable must be protected by a lock, so the lock must be held before the condition predicate can be tested. The lock object and the conditional queue object (and the object where the methods such as wait and notify are called) must be the same object.
3). Constraint: each call to wait is implicitly associated with a specific condition predicate, and when a specific condition predicate is invoked, the caller must already hold a lock associated with the condition queue, which must also protect the state variables that make up the condition predicate

2. Conditional queue usage rules

1). There is usually a conditional predicate
2). Always test the conditional predicate before calling a wait, and again after returning in a wait;
3). Always call wait in the loop;
4). Ensure that the state variable constituting the condition predicate is protected by a lock that must be associated with the condition queue;
5). When wait, notify, and notifyAll are called, the lock associated with the condition queue is held;
6). Do not release the lock until the protected logic is executed after checking the condition predicate;

3. Inform

Use notifyAll as far as possible, rather than nofify. Because nofify random wake A thread from A dormant state to Blocked state (Blocked state is A kind of thread have been trying to get the status of the lock, lock is available, once found immediately hold locks), and notifyAll will wake all threads from hibernation to condition the queue Blocked state. Considering this kind of situation, if the thread A predicate Pa to enter A dormant state, because of the condition thread B predicate Pb to enter A dormant state. Because of the condition when the Pb is true, perform A single thread C Notify. If the JVM on randomly chose to thread A wake, then thread A predicate Pa inspection condition not to true and then entered into A dormant state. After that no other threads can be awakened, the program will have been dormant. If you are using A notifyAll is different, the JVM will wake all conditions the queue waiting thread from A dormant state to the Blocked state, even if the randomly selected A thread A predicate is not because of the condition is true in A dormant state, other threads will compete to continue to perform the lock.

4. Standard form of state-dependent methods


void stateDependentMethod throwsInterruptedException{
synchronized(lock){
while(!conditionPredicate))
lock.wait();
}
//dosomething();
.... notifyAll();
}

Display Condition object

The Condition object shown is a more flexible option that provides richer functionality: multiple waits can exist on each lock, conditional waits can be interruptible, time-based waits, and fair or unfair queue operations. An Condition can be associated with a Lock, just as a Condition queue is associated with a built-in Lock. To create an Condition, call the lock-newcondition method on the associated Lock. The following reimplements the bounded cache with the display condition variable


public class ConditionBoundedBuffer<V> {
 private final V[] buf;
 private int tail;
 private int head;
 private int count;
 private Lock lock = new ReentrantLock();
 private Condition notFullCondition = lock.newCondition();
 private Condition notEmptyCondition = lock.newCondition();
 @SuppressWarnings("unchecked")
 public ConditionBoundedBuffer(int capacity) {
  buf = (V[]) new Object[capacity];
 }  public void doPut(V v) throws InterruptedException {
  try {
   lock.lock();
   while (count == buf.length)
    notFullCondition.await();
   buf[tail] = v;
   if (++tail == buf.length)
    tail = 0;
   count++;
   notEmptyCondition.signal();
  } finally {
   lock.unlock();
  }  }  public V doTake() throws InterruptedException {
  try {
   lock.lock();
   while (count == 0)
    notEmptyCondition.await();
   V v = buf[head];
   buf[head] = null;
   if (++head == buf.length)
    head = 0;
   count--;
   notFullCondition.signal();
   return v;
  } finally {
   lock.unlock();
  }
 }
}


Related articles: