java concurrent programming (2): Java multithreading-java.util.concurrent advanced tool

Advanced multi-thread control class

Java1.5 provides a very efficient and practical multi-threaded package: java.util.concurrent, which provides a large number of advanced tools to help developers write efficient, easy-to-maintain, and clearly structured Java multi-threaded programs.

ThreadLocal class

The ThreadLocal class is used to save independent variables of threads. For a thread class (inherited from Thread)

When using ThreadLocal to maintain variables, ThreadLocal provides an independent copy of the variable for each thread that uses the variable, so each thread can independently change its own copy without affecting the corresponding copies of other threads . Commonly used for user login control, such as recording session information.

Implementation: Each Thread holds a variable of type TreadLocalMap (this class is a lightweight Map, the function is the same as map, the difference is that entries are placed in the bucket instead of a linked list of entries. The function is still a map.) Itself is the key and the target is the value.

The main methods are get() and set(T a). After set, a threadLocal -> a is maintained in the map, and a is returned when getting. ThreadLocal is a special container.

Atomic classes (AtomicInteger, AtomicBoolean...)

If you use an atomic wrapper class such as atomicInteger, or use your own guaranteed atomic operation, it is equivalent to synchronized

AtomicInteger.compareAndSet(int expect,int update)//The return value is boolean

AtomicReference

For AtomicReference, the object may have properties lost, that is, oldObject == current, but oldObject.getPropertyA != current.getPropertyA.

At this time, AtomicStampedReference comes in handy. This is also a very common idea, that is, adding the version number

Lock class 

lock: in the java.util.concurrent package. There are three implementations:

  1. ReentrantLock

  2. ReentrantReadWriteLock.ReadLock

  3. ReentrantReadWriteLock.WriteLock

The main purpose is the same as synchronized. Both are technologies created to solve synchronization problems and handle resource disputes. The functionality is similar but there are some differences.

The differences are as follows:

  1. lock is more flexible, and you can freely define the order of unlocking the shackles of multiple locks (synchronized must be added first and then unlocked)

  2. Provides a variety of locking schemes, lock blocking, trylock non-blocking, lockInterruptily interruptible, and trylock version with timeout.

  3. Essentially the same as monitor lock (ie synchronized)

  4. With great power comes greater responsibility, and locking and unlocking must be controlled, otherwise disaster may result.

  5. Combination with Condition class.

  6. Higher performance, performance comparison between synchronized and Lock, as shown below:

Use of ReentrantLock

The meaning of reentrancy is that the thread holding the lock can continue to hold it, and the lock must be released an equal number of times before the lock is actually released.

private java.util.concurrent.locks.Lock lock = new ReentrantLock();
public void method() {	
	try {		
		lock.lock(); //obtain lock, synchronization block
	} finally {		
		lock.unlock();//Release the lock
	}
}

  • ReentrantLock is more powerful than synchronized, mainly reflected in:

  • ReentrantLock has a choice of fair strategies.

  • ReentrantLock can be acquired conditionally when acquiring the lock, and the waiting time can be set, effectively avoiding deadlock.

  • Such as tryLock() and tryLock(long timeout, TimeUnit unit)

  • ReentrantLock can obtain various information about the lock and is used to monitor various statuses of the lock.

  • ReentrantLock can flexibly implement multiple notifications, that is, the application of Condition.

Fair lock and unfair lock

ReentrantLock defaults to an unfair lock, allowing threads to "preempt the queue" to acquire the lock. Fair lock means that threads acquire locks in the order requested, similar to the FIFO strategy.

Use of locks

  1. lock() acquires the lock in a blocking manner, and only processes the interrupt information after acquiring the lock.

  2. lockInterruptibly() acquires the lock blockingly, handles interrupt information immediately, and throws an exception

  3. tryLock() attempts to acquire the lock. Regardless of success or failure, it will immediately return true or false. Note that even if the lock has been set to use a fair sorting strategy, tryLock() can still turn on fairness to jump in the queue and preempt it. If you want to respect fair setting of this lock, use tryLock(0, TimeUnit.SECONDS), which is almost equivalent (also detects interrupts).

  4. tryLock(long timeout, TimeUnit unit) acquires the lock in a blocking manner within the timeout time, returns true successfully, returns false upon timeout, and processes the interrupt information immediately and throws an exception.

If you want to use a timed tryLock that allows breaking into fair locks, you can combine the timed and untimed forms:

if (lock.tryLock() || lock.tryLock(timeout, unit) ) { ... }

private java.util.concurrent.locks.ReentrantLock lock = new ReentrantLock();
public void testMethod() {	
	try {		
		if (lock.tryLock(1, TimeUnit.SECONDS)) {
			//Get the lock and synchronization block
		} else {		
			//The lock was not obtained
		}
	} catch (InterruptedException e) {
		e.printStackTrace();
	} finally {
		if (lock.isHeldByCurrentThread())
		//If the current thread holds the lock, release the lock
			lock.unlock();
	}
}

Use of Condition

Conditions can be created by locks to implement a multi-channel notification mechanism.

Methods with await, signal, and signalAll are similar to wait/notify and need to be called after acquiring the lock.

private final java.util.concurrent.locks.Lock lock = new ReentrantLock();
private final java.util.concurrent.locks.Condition condition = lock.newCondition();
public void await() {	
	try {		
		lock.lock(); //obtain lock
		condition.await();//Wait for condition communication signal and release condition lock
		//Receive condition communication
	} catch (InterruptedException e) {
		e.printStackTrace();
	} finally {
		lock.unlock();//Release the object lock
	}
}

Use of ReentrantReadWriteLock

ReentrantReadWriteLock is a further extension of ReentrantLock. It implements the read lock readLock() (shared lock) and the write lock writeLock() (exclusive lock) to realize the separation of reading and writing. Reading and reading are not mutually exclusive, only reading and writing, writing and reading, and writing and writing are mutually exclusive, which improves the performance of reading and writing.

Read lock example:

private final java.util.concurrent.locks.ReadWriteLock lock = new ReentrantReadWriteLock();
public void method() {	
	try {		
		lock.readLock().lock();//obtain read lock readLock, synchronization block
	} finally {		
		lock.readLock().unlock();//Release the read lock readLock
	}
}

Write lock example:

private final java.util.concurrent.locks.ReadWriteLock lock = new ReentrantReadWriteLock();
public void method() {	
	try {		
		lock.writeLock().lock(); //obtain write lock writeLock, synchronization block
	} finally {
		lock.writeLock().unlock(); //Release the write lock writeLock
	}
}

Container class

Overview of synchronous and asynchronous containers

Sync containers

Includes two parts:

  • One is Vector and Hashtable of early JDK;

  • One is their homologous container, the synchronization wrapper class added in JDK1.2, which is created using the Collections.synchronizedXxx factory method.

Map<String, Integer> hashmapSync = Collections.synchronizedMap(new HashMap<>());

Synchronous containers are thread-safe, and only one thread can access the container's state at a time .

However, in some scenarios, locking may be required to protect compound operations .

Compound class operations such as: add, delete, iterate, jump and conditional operations.

These compound operations may exhibit unexpected behavior when multiple threads modify the container concurrently.

The most classic one is ConcurrentModificationException.

The reason is that when the container iterates, the content is modified concurrently. This is because the early iterator design did not consider the issue of concurrent modification.

The underlying mechanism is nothing more than using the traditional synchronized keyword to synchronize each public method, so that only one thread can access the state of the container at a time. This obviously does not meet the high concurrency needs of our Internet era today. While ensuring thread safety, it must also have good enough performance.

concurrent container

Compared with Collections.synchronizedXxx() synchronization containers, etc., the concurrent container introduced in util.concurrent mainly solves two problems:

  1. Design according to specific scenarios, try to avoid synchronization and provide concurrency .

  2. Defines some concurrency-safe compound operations and ensures that iterative operations in a concurrent environment will not go wrong .

When the container in util.concurrent is iterating, it does not need to be encapsulated in synchronized, and it can guarantee that no exception will be thrown, but it may not be the "latest and current" data that you see every time.

Map<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();

ConcurrentHashMap replaces the synchronized Map (Collections.synchronized(new HashMap())).

As we all know, HashMap is stored in segments based on hash values. Synchronous Maps will lock the entire Map during synchronization , while ConcurrentHashMap introduces segment definitions when designing storage. During synchronization, you only need to lock based on hash values. Just focus on the paragraph where the hash value is located, which greatly improves performance . ConcurrentHashMap also adds support for common compound operations, such as "if not added": putIfAbsent(), replacement: replace(). These two operations are both atomic operations. Note that ConcurrentHashMap weakens the size() and isEmpty() methods, and uses them as little as possible in concurrent situations to avoid possible locking (of course, it is also possible to obtain the value without locking, if the number of maps does not change).

CopyOnWriteArrayList and CopyOnWriteArraySet replace List and Set respectively. They are mainly used to replace synchronous List and synchronous Set when traversal operations are the main ones. This is the idea mentioned above: the iteration process must ensure that no errors occur. In addition to locking, another One way is to "clone" the container object. ---The disadvantages are also obvious. It occupies memory and the data is ultimately consistent, but the data may not be consistent in real time. It is generally used in concurrent scenarios with more reading and less writing.

  • ConcurrentSkipListMap can replace SoredMap in efficient concurrency (such as TreeMap wrapped with Collections.synchronzedMap).

  • ConcurrentSkipListSet can replace SoredSet in efficient concurrency (such as TreeMap wrapped with Collections.synchronzedSet).

  • ConcurrentLinkedQuerue is a first-in-first-out queue. It is a non-blocking queue. Be careful to use isEmpty instead of size();

Use of CountDownLatch latching

Management

The concept of management class is relatively general and is used to manage threads. It is not multi-threaded itself, but it provides some mechanisms to use the above tools to do some encapsulation.

The management classes worth mentioning that I learned about: ThreadPoolExecutor and the system-level management class ThreadMXBean under the JMX framework

ThreadPoolExecutor

If you don’t know this class, you should know about the ExecutorService mentioned earlier. It is very convenient to open your own thread pool:

ExecutorService e = Executors.newCachedThreadPool();
    ExecutorService e = Executors.newSingleThreadExecutor();
    ExecutorService e = Executors.newFixedThreadPool(3);
    // The first is a variable-size thread pool, which allocates threads according to the number of tasks.
    //The second is a single-thread pool, equivalent to FixedThreadPool(1)
    // The third type is a fixed size thread pool.
    // then run
    e.execute(new MyRunnableImpl());

This class is internally implemented through ThreadPoolExecutor. Mastering this class helps to understand the management of thread pools. In essence, they are all various implementation versions of the ThreadPoolExecutor class.

Reference article:

Java multi-threaded concurrent programming overview transcript  https://www.cnblogs.com/yw0219/p/10597041.html

For multi-threading in Java, you only need to read this article https://juejin.im/entry/57339fe82e958a0066bf284f

Reprint the article " Java Concurrent Programming (2): Java Multithreading - java.util.concurrent Advanced Tools ",
please indicate the source: java Concurrent Programming (2): Java Multithreading - java.util.concurrent Advanced Tools - java Focus on review content and detailed explanation of core concepts - Zhou Junjun's personal website

Guess you like

Origin blog.csdn.net/u012244479/article/details/130049701