Lock of concurrent programming in Java

ReentrantLock

Can replace synchronized, but synchronized is more flexible.
However, the lock must be released manually.

try {
    lock.lock();
} finally {
    lock.unlock();
}

A reentrant lock means that any thread can acquire the lock again without being blocked after acquiring the lock.
For ReentrantLock, when releasing the lock, the lock calls the lock() method n times, then it needs to call n times when releasing the lock unlock() method.

  • tryLock() method, tryLock(long timeout, TimeUnit unit) method
    Try to lock,
    this method has a return value, the lock returns true, otherwise it returns false.
    If the lock cannot be locked or cannot be locked within a certain period of time, the thread can decide whether to wait.
  • lockInterruptibly() method
    The thread is blocked when requesting a lock. If interrupted, the thread will be woken up and asked to handle InterruptedException.

Obtain the synchronization state logic again:
determine whether the acquisition operation is successful by judging whether the current thread is the thread that acquires the lock. If it is the thread that acquires the lock, the synchronization state is increased and true is returned, indicating that the acquisition of the synchronization state is successful.
If it is a fair lock, It is also necessary to judge whether the current node in the synchronization queue has a predecessor node, and if so, it needs to wait for the predecessor thread to acquire the lock and release it before continuing to acquire the lock.

Fair locks and unfair locks
Whether it is fair or not depends on acquiring locks. Fairness means that the order of acquiring locks is in the order of request time, that is, FIFO.
ReentrantLock is an unfair lock by default, and the parameterized construction is called when constructing an object. method and enter true, that is, a fair lock.

Read-write lock ReentrantReadWriteLock

The read-write lock allows multiple reader threads to access at the same time, but when the write thread accesses, all read and write threads will be blocked. The read-
write lock maintains a read lock and a write lock. And, follow the acquisition of write locks The order in which the read lock is acquired and the write lock is released, the write lock can be ranked as a read lock (that is, the lock is downgraded )

ReentrantReadWriteLock also supports reentrancy and fairness selection

ReentrantReadWriteLock implements the ReadWriteLock interface, which only defines methods for acquiring read locks and write locks, namely readLock() and writeLock(). ReentrantReadWriteLock also provides the following methods:
| Method Name | Description |
| :-- ------ | :-------- |
| int getReadLockCount() | Returns the number of times the current read lock is acquired. The number of times is not equal to the number of threads that acquire the read lock, a thread can acquire the read lock multiple times lock|
| int getReadHoldCount() | Returns the number of times the current thread acquires the read lock, and saves it in ThreadLocal |
| boolean isWriteLocked() | Determines whether the write lock is acquired|
| int getWriteHoldCount() | Returns the number of times the current write lock is acquired|

ReentrantReadWriteLock maintains the acquisition state of the read-write lock by dividing the synchronization state of AQS into high 16 bits (read) and low 16 bits (write). The state in the

figure indicates that the current thread has acquired the write lock and reentrant two times, two read locks are acquired at the same time. The read-
write lock is determined by bit operation to determine the read-write state.
If the current synchronization state is S, then: the
write state is S & 0x0000FFFF, that is, the high-order 16 bits (read) are erased
The read state is S >>> 16, that is, shift right by 16 bits When the
write state increases by 1, S + 1
When the read state increases by 1, S + (1 << 16), that is, S + 0x00010000
Inference: the synchronization state S is not equal to 0 When the write status (S & 0x0000FFFF) is equal to 0, the read status (S >>> 16) is greater than 0, that is, the read lock has been acquired.

Acquiring and releasing write locks

The write lock supports reentrancy , but it is an exclusive lock .
When the current thread acquires the write lock:

  1. If the current thread has acquired the write lock, the write state is incremented
  2. If the read lock has been acquired or the thread is not the thread that has acquired the write lock, the current thread is in a waiting state

    protected final boolean tryAcquire(int acquires) {
    /*
     * Walkthrough:
     * 1. if read count nonzero or write count nonzero
     *     and owner is a different thread, fail.
     * 2. If count would saturate, fail. (This can only
     *    happen if count is already nonzero.)
     * 3. Otherwise, this thread is eligible for lock if
     *    it is either a reentrant acquire or
     *    queue policy allows it. If so, update state
     *    and set owner.
     */
    Thread current = Thread.currentThread();
    int c = getState();
    int w = exclusiveCount(c); // 获取写状态
    if (c != 0) {
        // (Note: if c != 0 and w == 0 then shared count != 0), 即上面的推论
        if (w == 0 || current != getExclusiveOwnerThread()) // 后面是重入条件
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
    }
    if ((w == 0 && writerShouldBlock(current)) ||
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
    }

    If there is a read lock, the write lock cannot be acquired because:
    the read-write lock must ensure that the operation of the write lock is visible to the read lock. If the read lock is allowed to acquire the write lock when it has already been acquired, then the running reader thread The operation of the writing thread cannot be sensed.

The release of the write lock means that the write state decreases each time the write state is released. When the write state is 0, it means that the write lock has been released.

Read lock acquisition and release

Read locks are shared locks that support reentrancy . Read locks can be acquired by multiple threads at the same time. In the absence of access by writer threads, read locks can always be acquired successfully (plus read status).

Condition interface

Before Java5, the implementation of wait/notify mode can be realized by using wait()/notify()/notifyAll() with synchronized.

The above methods are available on any Java object and are called monitor methods

The Condition interface also provides similar methods:

method name describe
await() The current thread enters a wait state until notified or interrupted
awaitUninterruptibly() The current thread enters a wait state until notified
awaitNanos(long nanosTimeout) The current thread enters the waiting state until it is notified or interrupted or times out, and the return value is the remaining time
awaitUtil(Date deadLine) The current thread enters a waiting state until it is notified or interrupted or a certain time is reached, and it returns true before being notified at a certain time
signal() Wakes up a thread waiting on a Condition that must acquire the lock associated with the Condition before returning from the wait method
signalAll() Wake up all threads waiting on the Condition, threads that can return from the wait method must acquire the lock associated with the Condition

Obtaining a Condition object must be done through Lock's newCondition() method.

Lock lock = new ReentrantLock();
Condition con1 = lock.newCondition();
Condition con2 = lock.newCondition();

When using it, you must first acquire the lock, and then call the condition method:

public class MyContainer2<T> {
    final private LinkedList<T> lists = new LinkedList<>();
    final private int MAX = 10; //最多10个元素
    private int count = 0;
    
    private Lock lock = new ReentrantLock();
    private Condition producer = lock.newCondition();
    private Condition consumer = lock.newCondition();
    
    public void put(T t) {
        try {
            lock.lock();
            while(lists.size() == MAX) { //想想为什么用while而不是用if?
                producer.await();
            }
            
            lists.add(t);
            ++count;
            consumer.signalAll(); //通知消费者线程进行消费
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    
    public T get() {
        T t = null;
        try {
            lock.lock();
            while(lists.size() == 0) {
                consumer.await();
            }
            t = lists.removeFirst();
            count --;
            producer.signalAll(); //通知生产者进行生产
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return t;
    }
    
    public static void main(String[] args) {
        MyContainer2<String> c = new MyContainer2<>();
        //启动消费者线程
        for(int i=0; i<10; i++) {
            new Thread(()->{
                for(int j=0; j<5; j++) System.out.println(c.get());
            }, "c" + i).start();
        }
        
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        //启动生产者线程
        for(int i=0; i<2; i++) {
            new Thread(()->{
                for(int j=0; j<25; j++) c.put(Thread.currentThread().getName() + " " + j);
            }, "p" + i).start();
        }
    }
}

Reference: "The Art of Java Concurrent Programming"

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324522199&siteId=291194637