6. What is Cancarenzi?
Concurrency is a class library in Java that contains special classes optimized for working across multiple threads. These classes are collected in a packagejava.util.concurrent
. They can be schematically divided according to functionality as follows: Concurrent Collections - a set of collections that work more efficiently in a multi-threaded environment than 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 multi-threading support. Non-blocking queues are designed for speed and operation without blocking threads. Blocking queues are used when you need to “slow down” the “Producer” or “Consumer” threads if some conditions are not met, for example, the queue is empty or overflowed, or there is no free “Consumer”. Synchronizers are auxiliary utilities for synchronizing threads. They are a powerful weapon in “parallel” computing. Executors - contains excellent frameworks for creating thread pools, scheduling asynchronous tasks and obtaining results. Locks - represents alternative and more flexible thread synchronization mechanisms compared to the basic synchronized
ones . Atomics - classes with support for atomic operations on primitives and references. Source:wait
notify
notifyAll
7. What classes from Kankarensi do you know?
The answer to this question is perfectly stated in this article . I don’t see the point in reprinting it all here, so I will give descriptions only of those classes that I had the honor of briefly familiarizing myself with. ConcurrentHashMap<K, V> - UnlikeHashtable
and blocks synhronized
on HashMap
, the data is presented in the form of segments, divided into hashes of keys. As a result, data is accessed by segments rather than by single object. In addition, iterators represent data for a specific period of time and do not throw ConcurrentModificationException
. AtomicBoolean, AtomicInteger, AtomicLong, AtomicIntegerArray, AtomicLongArray - What if in a class you need to synchronize access to one simple variable of type int
? You can use constructs with synchronized
, and when using atomic operations set/get
, volatile
. But you can do even better by using new classes 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 time of its introduction,ConcurrentHashMap
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 when performing a read operation
ConcurrentHashMap
are as follows:
-
Map elements
Unlike elements
HashMap
,Entry
inConcurrentHashMap
are declared asvolatile
. This is an important feature, also due 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); }
Why is there a need to make the hash function more complex? The tables in a hash map have a length determined by a power of two. For hash codes whose binary representations do not differ in low and high position, we will have collisions. Increasing the complexity of 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 is a power of two). Each segment is a thread-safe table of map elements. Increasing the number of segments will encourage modification operations to span multiple segments, reducing the likelihood of blocking at runtime.
-
ConcurrencyLevel
This parameter affects the memory card usage and the number of segments in the card.
The number of segments will be chosen as the nearest power of two greater than concurrencyLevel. Lowering the concurrencyLevel makes it more likely that threads will 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
:hashmap
The map has an interaction interface similar to- Read operations do not require locks and are performed in parallel
- Write operations can often also be performed in parallel without blocking
- When creating, the required one is indicated
concurrencyLevel
, determined by reading and writing statistics - Map elements have a value
value
declared asvolatile
9. What is the Lock class?
To control access to a shared resource, we can use locks as an alternative to the synchronized operator. 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. Once the work is completed, the lock on the shared resource is released. If the resource is not free and a lock is already placed 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 the lock is acquiredboolean tryLock():
tries to acquire a lock; if the lock is obtained, it returns true . If the lock is not acquired, it returns false . Unlike the method,lock()
it does not wait to acquire a lock if one is not availablevoid unlock():
removes the lockCondition newCondition():
returns the objectCondition
that is associated with the current lock
lock()
, and after finishing working with shared resources, the method is called unlock()
, which releases the lock. The object Condition
allows you to manage blocking. As a rule, to work with locks, a class ReentrantLock
from the package is used. java.util.concurrent.locks.
This class implements the interface Lock
. Let's look at using the Java Lock API using a small program as an example: 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(){
// потокобезопасность для логгирования нам не требуется
}
}
Now let’s take a class that implements the interface Runnable
and uses class methods 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 obtain a lock on the object, it simply logs and exits. Another important point. You must use a block try-finally
to ensure that the lock will be 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 exclusive ownership of an object, it marks its mutex as busy, 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 the mutex, which ensures correct operation with it. After all, it is not enough to mark that the object is busy; we must also ensure that other threads do not try to use the busy object. In Java, the monitor is implemented using the keywordsynchronized
. 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
a code is added that marks the mutex as free. - Before the block,
synchronized
code is added that checks if the mutex is busy, then the thread must wait for it to be released.
GO TO FULL VERSION