Java concurrent programming (4) Common synchronization tools
The synchronization tool class can enable any type of object, as long as the object can coordinate and control the control flow of the thread according to its own State. Blocking queues can be used as synchronization tools. Other types of synchronization tools also include:Semaphores(Semaphore ),Barrier(Barrier)And locking(Latch ).
Locking
First, we will introduce locking.
Locking is equivalent to a door: before locking to a certain state, the door is always closed, and all threads will wait (blocking) in front of the door ). Only when the door is opened will all threads continue to run at the same time.
Blocking can be used to ensure that certain activities are not executed until other activities are completed. For example:
1. Make sure that a computing task is executed after all its resources are initialized. Binary locks (only two States) can be used to indicate that "resource R has been initialized", and all R operations must wait for this lock first.
2. Make sure that a service is started only after all other services are started. Multiple locks are required. Let S wait for each lock. the operation will continue only after all locks are enabled.
3. Wait until the participants (for example, players in multi-player games) of an operation are ready to continue. In this case, when all players are ready, the lock will end.
CountDownLatch is a flexible locking implementation that can be used in the above situations. The lock status contains a counter, initialized to a positive number, indicating the number of events to wait. The countDown () method will decrease the counter, indicating that one of the waiting events has occurred. The await () method is blocked until the counter value changes to 0.
Next, we use locks to implement the function of calculating the running time of multiple sub-threads in the main thread. The specific logic is to use two locks. The "start gate" is used to control the sub-thread running at the same time, and the "End Gate" is used to identify whether the Sub-thread ends.
Package org. bupt. xiaoye; import java. util. concurrent. countDownLatch; public class Test {public static int nThread = 5; public static void main (String [] args) throws InterruptedException {final CountDownLatch startGate = new CountDownLatch (1 ); final CountDownLatch endGate = new CountDownLatch (nThread); for (int I = 0; I
FutureTask
FutureTask can also be used as a lock. It indicates an abstract computation that can generate results. It is implemented through Callable, which is equivalent to a Runnable that can generate results and can be in the following three States: Waiting for running, running, and running completed. When FutureTask enters the completion state, it will stay in this state.
Future. get is used to obtain the calculation result. If FutureTask is not completed yet, it will be blocked. FutureTask transmits the computing result from the execution thread to the thread that obtains the result. The FutureTask specification ensures the safe release of the result in this transfer process.
FutureTask indicates an asynchronous task in the Executor framework. It can also be used to represent some long computations that can be started before the calculation results are used.
Next we will construct a simple asynchronous task to demonstrate how to use FutureTask.
package org.bupt.xiaoye;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;public class Preloader {private final FutureTask
future = new FutureTask
(new Callable
() {public Integer call() throws Exception {Thread.sleep(3000);return 969;}});private final Thread thread = new Thread(future);public void start() {thread.start();}public Integer get() throws Exception {try{return future.get();}catch(ExecutionException e){Throwable cause = e.getCause();throw launderThrowable(cause);}}private static Exception launderThrowable(Throwable cause) {if(cause instanceof RuntimeException)return (RuntimeException )cause;else if(cause instanceof Error) throw (Error) cause;else throw new IllegalStateException("Not Checked",cause);}public static void main(String[] args) throws Exception{Preloader p = new Preloader();p.start();long start = System.currentTimeMillis();System.out.println(p.get());System.out.println(System.currentTimeMillis()-start);}}
Semaphores
Previously, locking controls the access time, while semaphores are used to control the number of operations to access a specific resource and control the space. Moreover, locking can only be reduced and used at one time, while semaphores can be applied for release and can be increased or decreased. A counting semaphore can also be used to implement a certain resource pool or impose a boundary on the container.
Semaphone manages this set of licenses (permit), which can be specified by constructors. The blocking method acquire is also provided to obtain the license. The release method is provided to release a license.
Semaphone can change any container to a bounded blocking container, for example, used to implement a resource pool. For example, the database connection pool. We can construct a fixed-length connection pool and use the blocking methods acquire and release to obtain and release the connection, instead of failing to get the connection. (Of course, it is easier to use BlockingQueue to save resources in the connection pool at the beginning of the design)
For example, we can change a common set container to a blocking bounded container.
package org.bupt.xiaoye;import java.util.Collections;import java.util.HashSet;import java.util.Set;import java.util.concurrent.Semaphore;public class BoundedHashSet
{private final Set
set;private Semaphore sem;public BoundedHashSet(int bound) {if (bound < 1)throw new IllegalStateException();set = Collections.synchronizedSet(new HashSet
());sem = new Semaphore(bound);}public boolean add(T e) throws InterruptedException {sem.acquire();boolean wasAdded = false;try {wasAdded = set.add(e);return wasAdded;} finally {if (!wasAdded)sem.release();}}public boolean remove(T e) {boolean wasRemoved = set.remove(e);if (wasRemoved)sem.release();return wasRemoved;}}
Barrier
Bariier is similar to blocking, which can block a group of threads to know that an event occurs. The key difference between a barrier and a lock is that all threads must reach the barrier position at the same time to continue execution. Blocking is used to wait for the wait time, while the fence is used to wait for the thread.
CyclicBarrier can aggregate a certain number of participants at the barrier position repeatedly. It is very useful in parallel Iteration Algorithms: splitting a problem into a series of independent subproblems. When a thread reaches the barrier position, call the await () method. This method is to block the method until all threads reach the barrier position. Then, the barrier is opened and all threads are released, the fence will be reset for next use.
Another form of fence is the Exchanger, which is a Two-Party fence where all parties exchange data. For example, when a thread wants to write data in the buffer zone, another thread reads data from the buffer zone. These threads can use Exchanger to converge and exchange slow buffers with empty buffers. When two threads exchange objects through Exchanger, this exchange securely releases these two objects to the other.
Exchanger may be considered as a bidirectional form of SynchronousQueue. We can also use two SynchronousQueue to implement the Exchanger function.
class FillAndEmpty { Exchanger
exchanger = new Exchanger
(); DataBuffer initialEmptyBuffer = ... a made-up type DataBuffer initialFullBuffer = ... class FillingLoop implements Runnable { public void run() { DataBuffer currentBuffer = initialEmptyBuffer; try { while (currentBuffer != null) { addToBuffer(currentBuffer); if (currentBuffer.isFull()) currentBuffer = exchanger.exchange(currentBuffer); } } catch (InterruptedException ex) { ... handle ... } } } class EmptyingLoop implements Runnable { public void run() { DataBuffer currentBuffer = initialFullBuffer; try { while (currentBuffer != null) { takeFromBuffer(currentBuffer); if (currentBuffer.isEmpty()) currentBuffer = exchanger.exchange(currentBuffer); } } catch (InterruptedException ex) { ... handle ...} } } void start() { new Thread(new FillingLoop()).start(); new Thread(new EmptyingLoop()).start(); } }