The third part of JAVA multithreading (1) Explicit lock and synchronized

Concurrent Notes Portal:
1.0 Concurrent Programming-Mind Map
2.0 Concurrent Programming-Thread Safety Fundamentals
3.0 Concurrent Programming-Basic Building Module
4.0 Concurrent Programming-Task Execution-Future
5.0 Concurrent Programming-Multi-threaded Performance and Scalability
6.0 Concurrent Programming- Explicit lock and synchronized
7.0 concurrent programming-AbstractQueuedSynchronizer
8.0 concurrent programming-atomic variables and non-blocking synchronization mechanism

Explicit lock

Before Java 5, the only mechanism that can be used when coordinating access to shared objects is synchronizedsum volatile. Java 5 has been added ReentrantLock. ReentrantLockIt is not a way to replace the built-in locking, but as an optional advanced function when the built-in locking mechanism is not applicable.

Lock 与 ReentrantLock

Lock provides an unconditional, pollable, timed, and interruptible acquisition operation. All locking and unlocking methods are explicit.

In the implementation of Lock, the same memory visibility semantics as internal locks must be provided, but they can be different in terms of locking semantics, scheduling algorithms, order guarantees, and performance characteristics.

package java.util.concurrent.locks;
/**
 * @see ReentrantLock
 * @see Condition
 * @see ReadWriteLock
 *
 * @since 1.5
 * @author Doug Lea
 */
public interface Lock {
    
    

    void lock();

    void lockInterruptibly() throws InterruptedException;

    boolean tryLock();

    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    void unlock();

    Condition newCondition();
}

ReentrantLockImplements Lockthe interface, and provides the synchronizedvisible memory and the same row of the mutex. And synchronizedlike the same, ReentrantLockit also provides 可重入(可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。)locking semantics.

Why create a new locking mechanism that is so similar to memory locks?

In most cases, the built-in lock works well, but there are some limitations in functionality. For example, you cannot interrupt a thread that is waiting to acquire a lock, or you cannot wait indefinitely while requesting a lock.
The built-in lock must be released in the code block that acquired the lock, which simplifies the coding work and achieves a good interaction with exception handling operations, but it cannot implement the locking rules of the non-blocking structure. These are synchronizedthe reasons for use , but in some cases, a more flexible locking mechanism can usually provide better liveness or performance.

To call Lock explicitly, the lock must be released in finally. Although it is not difficult to release the lock in finally, it may be forgotten.

Polling lock and time lock

The timed and pollable lock acquisition mode is tryLockrealized by the method. Compared with the unconditional lock acquisition mode, it has a more complete error recovery mechanism.

Interruptible lock acquisition operation

lockInterruptibly The method can maintain the response to the interrupt while obtaining the lock.

The code is below Log! ! ! ! !

The lock() method prints: After thread 1 cannot obtain the lock, it will wait for the release of the lock and will not respond to the interrupt. When thread 0 releases the lock, thread 1 resumes its response to the interrupt.

Thread-0:start get lock
Thread-0:already get lock
Thread-1:start get lock
Thread-0:working num 0
Thread-0:working num 1
Thread-0:working num 2
Thread-0:working num 3
Thread-0:working num 4
Thread-0:working num 5
Thread-0: release unlock
Thread-1:already get lock
Thread-1:Interrupt
Thread-1: release unlock

The lockInterruptibly() method prints: Thread 1 can respond to the interrupt in time after it cannot obtain the lock.

Thread-0:start get lock
Thread-0:already get lock
Thread-1:start get lock
Thread-1:Interrupt
Thread-1: unlock failed
Thread-1: failed desc:null
Thread-0:working num 0
Thread-0:working num 1
Thread-0:working num 2
Thread-0:working num 3
Thread-0:working num 4
Thread-0:working num 5
Thread-0: release unlock

Run the sample code lockInterruptiblyand lockthe difference will be clear .


    public static void main(String[] args) throws InterruptedException {
    
    
        LockTest lockTest = new LockTest();
        Thread t0 = new Thread(new Runnable(){
    
    
            @Override
            public void run() {
    
    
                lockTest.doWork();
            }
        });
        Thread t1 = new Thread(new Runnable(){
    
    
            @Override
            public void run() {
    
    
                lockTest.doWork();
            }
        });
        // 启动线程t1
        t0.start();
        Thread.sleep(10);
        // 启动线程t2
        t1.start();
        Thread.sleep(100);
        // 线程t1没有得到锁,中断t1的等待
        t1.interrupt();
    }

class LockTest {
    
    
    private Lock lock = new ReentrantLock();
    public void doWork() {
    
    
        String name = Thread.currentThread().getName();
        try {
    
    
            System.out.println(name + ":start get lock");
            //lock.lock();
            lock.lockInterruptibly();
            System.out.println(name + ":already get lock");
            for (int i = 0; i < 6; i++) {
    
    
                Thread.sleep(1000);
                System.out.println(name + ":working num "+ i);
            }
        } catch (InterruptedException e) {
    
    
            System.out.println(name + ":Interrupt");
        }finally{
    
    
            try {
    
    
                lock.unlock();
                System.out.println(name + ": release unlock");
            } catch (Exception e) {
    
    
                System.out.println(name + ": unlock failed");
                System.out.println(name + ": failed desc:" + e.getMessage());
            }

        }
    }
}

Performance considerations

When Java 5 is added ReentrantLock, it can provide better competitive performance than built-in locks. Java 6 uses an improved algorithm to manage built-in locks, so that built-in locks ReentrantLockare almost the same in throughput, and their scalability is basically the same.

Fairness

There ReentrantLockare two fairness choices in the constructor: create an unfair lock (default) or a fair lock.

  • Fair lock: Threads acquire locks in the order in which they make requests.
  • Unfair lock: If the state of the lock becomes available while the thread is making a request, then the thread will skip all waiting threads and acquire the lock.

In a fair lock, if another thread is holding the lock or another thread is waiting for the lock in the queue, the newly requested thread will be put into the queue. In an unfair lock, only when the lock is held by a thread, the newly requested thread will be put into the queue.

Why don't we want all locks to be fair?
When performing locking operations, the overhead of using fair locks in suspending and resuming threads can greatly reduce performance. And in actual situations, statistical fairness guarantees (to ensure that the blocked thread can finally obtain the lock) are usually sufficient, and the overhead will be much smaller. Some rely on fair queuing algorithms to ensure the correctness of the business, but these algorithms are not common. In most cases, the performance of unfair locks is higher than that of fair locks.

In synchronizedand ReentrantLockchoose between

ReentrantLockThe semantics provided on locks and memory are the same as the built-in locks. In addition, it also provides some other functions: timing lock waiting, interruptible lock waiting, fairness, and non-block-structured locking.

Compared with explicit locks, built-in locks still have great advantages. The built-in lock is more familiar to developers, and is simple and compact. ReentrantLockThe danger is higher than the synchronization mechanism. If you forget finallyto call it in the block, unlock()a time bomb has actually been planted.

In some cases where the built-in lock cannot meet the needs, it ReentrantLockcan be used as an advanced tool. It should be used when some advanced functions are needed ReentrantLock. These functions include: timed, pollable and interruptible acquisition operations, fair queues, and non-block structure locks. Otherwise, use it first synchronized.

Read-write lock

ReentrantLockA standard mutex lock is implemented: at most one thread holds each time ReentrantLock. But for maintaining the integrity of the data, mutual exclusion is usually an overly strong locking rule, which limits concurrency. Mutual exclusion is a conservative locking strategy. Although conflicts can be avoided 写/写, 写/读conflicts can also be avoided 读/读. Therefore, if 读/读the locking requirements of the situation are relaxed , the performance of the program will be improved. In this case, there is 读-写锁: a resource can be accessed by multiple read operations, or accessed by one write operation, but the two cannot be performed at the same time.

public interface ReadWriteLock {
    
    
    Lock readLock();
    Lock writeLock();
}

In 读-写锁the locking strategy, multiple read operations are allowed at the same time, but only one write operation is allowed at a time.

ReentrantReadWriteLockProvides important locking semantics for both types of locks. And ReentrantLocksimilar,
ReentrantReadWriteLockwhen construction can also choose to be a fair one non-lock (default) or a fair lock.

  • In a fair lock, the thread with the longest waiting time will get the lock first. If this lock is held by a reader thread and another thread requests a write lock, no other reader threads can acquire the read lock until the writer thread runs out and releases the writer lock.
  • In an unfair lock, the order in which threads obtain access permissions is uncertain. It is possible to downgrade a writer thread to a reader thread, but it is not possible to upgrade from a reader thread to a writer thread (this will cause a deadlock).

Example of read-write lock code:

public class ReadWriteMap<K,V>{
    
    
    private final Map<K,V> map;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock read = lock.readLock();
    private final Lock write = lock.writeLock();
    public ReadWriteMap(Map<K,V> map){
    
    
        this.map = map;
    }
    public V put(K key,V value){
    
    
        write.lock();
        try {
    
    
            return map.put(key, value);
        }finally{
    
    
            write.unlock();
        }
    }
    public V get(Object key){
    
    
        read.lock();
        try {
    
    
            return map.get(key);
        }finally{
    
    
            read.unlock();
        }
    }
}

Compared with the built-in lock, the explicit Lock provides some extended functions and has higher flexibility. Flexibility, and have better control over queue lines. But it ReentrantLockcannot be completely replaced synchronized. synchronizedIt should be used only when the demand cannot be met.

读-写锁Allows multiple reader threads to concurrently access the protected object. When accessing data structures dominated by read operations, it can improve the scalability of the program.

Guess you like

Origin blog.csdn.net/lijie2664989/article/details/105739247