6. What is cancarensi?
Concurrency is a class library in Java that has collected special classes that are optimized to work with multiple threads. These classes are collected in thejava.util.concurrent
. They can be schematically divided by functionality as follows: Concurrent Collections - a set of collections that work more efficiently in a multi-threaded environment than the standard universal collections from java.util
the package. Instead of a basic wrapper Collections.synchronizedList
with blocking access to the entire collection, locks on data segments are used or work is optimized for parallel reading of data using wait-free algorithms. Queues- non-blocking and blocking queues with multithreading support. Non-blocking queues are designed for speed and work without blocking threads. Blocking queues are used when it is necessary to "slow down" the "Producer" or "Consumer" threads if some conditions are not met, for example, the queue is empty or full, or there is no free "Consumer" 'a. Synchronizers are utility utilities for synchronizing threads. They are a powerful weapon in "parallel" computing. Executors - contains excellent frameworks for creating thread pools, scheduling the work of asynchronous tasks with obtaining results. Locks - is an alternative and more flexible thread synchronization mechanisms compared to the basic ones synchronized
,wait
, notify
, notifyAll
. Atomics are classes that support atomic operations on primitives and references. Source:
7. What classes from Cancarensi do you know?
The answer to this question is well stated in this article . I don’t see the point of reprinting all of it here, so I’ll give descriptions of only those classes that I had the honor to casually get acquainted with. ConcurrentHashMap<K, V> — UnlikeHashtable
blocks synhronized
on HashMap
, data is presented as segments broken down by key hashs. As a result, data access is locked by segments, not by one object. In addition, iterators represent data for a specific slice of time and do not throw ConcurrentModificationException
. AtomicBoolean, AtomicInteger, AtomicLong, AtomicIntegerArray, AtomicLongArray - What if the class needs to synchronize access to one simple variable of type int
? You can use structures withsynchronized
, and when using atomic operations set/get
, also works volatile
. But you can do even better by using the new Atomic*
. Due to the use of CAS, operations with these classes are faster than if synchronized via synchronized/volatile
. Plus, there are methods for atomic addition by a given amount, as well as increment / decrement.
8. How does the ConcurrentHashMap class work?
By the timeConcurrentHashMap
Java developers needed the following hash map implementation:
- thread safety
- No locks on the entire table while accessing it
- It is desirable that there are no table locks during a read operation.
ConcurrentHashMap
are as follows:
-
Map elements
Unlike elements
HashMap
,Entry
inConcurrentHashMap
are declared asvolatile
. This is an important feature, also related to changes in JMM .static final class HashEntry<K, V> { final K key; final int hash; volatile V value; final HashEntry<K, V> next; HashEntry(K key, int hash, HashEntry<K, V> next, V value) { this .key = key; this .hash = hash; this .next = next; this .value = value; } @SuppressWarnings("unchecked") static final <K, V> HashEntry<K, V>[] newArray(int i) { return new HashEntry[i]; } }
-
hash function
ConcurrentHashMap
an improved hashing function is also used.Let me remind you what it was like in
HashMap
JDK 1.2:static int hash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
Version from ConcurrentHashMap JDK 1.5:
private static int hash(int h) { h += (h << 15) ^ 0xffffcd7d; h ^= (h >>> 10); h += (h << 3); h ^= (h >>> 6); h += (h << 2) + (h << 14); return h ^ (h >>> 16); }
What is the need to complicate the hash function? The tables in a hash map are a power of two in length. For hash codes whose binary representations do not differ in the minor and major positions, we will have collisions. Complicating the hash function just solves this problem, reducing the likelihood of collisions in the map.
-
Segments
The map is divided into N different segments (16 by default, the maximum value can be 16 bits and represent a power of two). Each segment is a thread-safe table of map elements. Increasing the number of segments will encourage modification operations to affect different segments, which will reduce the chance of blocking during execution.
-
ConcurrencyLevel
This setting affects the memory card usage and the number of segments in the card.
The number of shards will be chosen as the nearest power of two greater than concurrencyLevel. Lowering the concurrencyLevel leads to the fact that threads are more likely to block map segments when writing. Overestimation of the indicator leads to inefficient use of memory. If only one thread will modify the map, and the rest will read, it is recommended to use the value 1.
-
Total
So, the main advantages and implementation features
ConcurrentHashMap
:- The map has a similar
hashmap
interaction interface - Read operations do not require locks and are performed in parallel
- Write operations can often also run in parallel without blocking.
- When creating, the required is specified
concurrencyLevel
, determined by read and write statistics - Map elements have a value
value
declared asvolatile
- The map has a similar
9. What is the Lock class?
As an alternative to the synchronized statement, we can use locks to control access to a shared resource. The locking functionality is packaged injava.util.concurrent.locks
. First, the thread tries to access the shared resource. If it is free, then a lock is placed on the thread on it. When the work is completed, the lock on the share is released. If the resource is not free and the lock is already on it, then the thread waits until this lock is released. Lock classes implement an interface Lock
that defines the following methods:
void lock():
waits until a lock is acquiredboolean tryLock():
tries to acquire the lock, if the lock is acquired then returns true . If the lock is not acquired, it returns false . Unlike a method,lock()
it does not wait to acquire a lock if it is not available.void unlock():
removes the lockCondition newCondition():
returns the objectCondition
that is associated with the current lock
lock()
, and after the work with shared resources is finished, the method is called unlock()
, which releases the lock. The object Condition
allows you to control the lock. ReentrantLock
As a rule, a class from the package is used to work with locks. java.util.concurrent.locks.
This class implements the Lock
. Let's consider using the Java Lock API on the example of a small program: So, let's say we have a class Resource
with a couple of thread-safe methods and methods where thread-safety is not required.
public class Resource {
public void doSomething(){
// пусть здесь происходит работа с базой данных
}
public void doLogging(){
// потокобезопасность для логгирования нам не требуется
}
}
And now we take a class that implements the interface Runnable
and uses the methods of the class Resource
.
public class SynchronizedLockExample implements Runnable{
// экземпляр класса Resource для работы с методами
private Resource resource;
public SynchronizedLockExample(Resource r){
this.resource = r;
}
@Override
public void run() {
synchronized (resource) {
resource.doSomething();
}
resource.doLogging();
}
}
Now let's rewrite the above program using the Lock API instead of the synchronized
.
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
// класс для работы с Lock API. Переписан с приведенной выше программы,
// но уже без использования ключевого слова synchronized
public class ConcurrencyLockExample implements Runnable{
private Resource resource;
private Lock lock;
public ConcurrencyLockExample(Resource r){
this.resource = r;
this.lock = new ReentrantLock();
}
@Override
public void run() {
try {
// лочим на 10 секунд
if(lock.tryLock(10, TimeUnit.SECONDS)){
resource.doSomething();
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
//убираем лок
lock.unlock();
}
// Для логгирования не требуется потокобезопасность
resource.doLogging();
}
}
As you can see from the program, we use the method tryLock()
to make sure that the thread only waits for a certain amount of time. If it does not acquire a lock on the object, then it simply logs and exits. Another important point. You must use a block try-finally
to ensure that the lock is released even if the method doSomething()
throws an exception. Sources:
11. What is a mutex?
A mutex is a special object for synchronizing threads/processes. It can take two states - busy and free. To simplify, a mutex is a boolean variable that takes two values: busy (true) and free (false). When a thread wants to have exclusive ownership of some object, it marks its mutex as occupied, and when it has finished working with it, it marks its mutex as free. A mutex is attached to every object in Java. Only the Java machine has direct access to the mutex. It is hidden from the programmer.12. What is a monitor?
A monitor is a special mechanism (a piece of code) - an add-on over a mutex that ensures proper work with it. After all, it is not enough to mark that the object is busy, you must also ensure that other threads do not try to use the busy object. In Java, a monitor is implemented using thesynchronized
. When we write a synchronized block, the Java compiler replaces it with three pieces of code:
- At the beginning of the block
synchronized
, code is added that marks the mutex as busy. - At the end of the block,
synchronized
code is added that marks the mutex as free. - Before the block,
synchronized
a code is added that looks, if the mutex is busy, then the thread must wait for it to be released.
GO TO FULL VERSION