Shared lock

Posted May 26, 20204 min read

Shared lock and exclusive lock

  • Shared lock:allows multiple threads to acquire locks simultaneously, such as Semaphore, CountDownLatch, ReadLock, etc.
  • Exclusive lock:only one thread can hold the lock at a time, such as ReentrantLock, synchronized, WriteLock, etc.

Implementation of shared lock in AQS

AQS provides exclusive locks and shared locks. Exclusive locks are implemented using acquire and release methods; shared locks are implemented using acquireShared and releaseShared. Let's take a look at how the shared lock is implemented in the source code. Take Semaphore and CountDownLatch as examples

Semaphore

Semaphore(semaphore):Controls the number of simultaneous access threads. A threshold can be set. Threads within the threshold can acquire locks at the same time. Threads exceeding the threshold need to wait for the lock to be released, which is somewhat similar to the thread pool.

Example:The company has five employees who want to go to the toilet, but the toilet has only three pits, then the other two people need to wait for the person in front to finish before entering, the code is as follows

public static void main(String []args) {
    ThreadFactory threadFactory = new ThreadNameFactory();
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 5, TimeUnit.SECONDS, new ArrayBlockingQueue <>(10), threadFactory);
    //There are 3 pits in the toilet in the company
    Semaphore semaphore = new Semaphore(3);
    //5 people go to pull the cake
    for(int i = 0; i <5; i ++) {
        People people = new People(i);
        threadPoolExecutor.execute(()-> {
            try {
                System.out.println(people.getName() + "Ready to go to the toilet");
                semaphore.acquire();
                System.out.println(people.getName() + "find the pit and start pulling the cake");
                Thread.sleep(3000); //Simulation of La Baba
                System.out.println(people.getName() + "After drawing the cake, prepare to go out of the toilet");
                semaphore.release();
                System.out.println(people.getName() + "Get out of the toilet");
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
    threadPoolExecutor.shutdown();
}

Semaphore sets the threshold through new Semaphore(3)

public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}

It can be seen here that the semaphore also supports fair lock, and the default is unfair lock. acquire() acquires the lock. This method calls AQS's tryAcquireShared method, and then implements this method in its own NonfairSync.

static final class NonfairSync extends Sync {
    ...
    protected int tryAcquireShared(int acquires) {
        return nonfairTryAcquireShared(acquires);
    }
}
final int nonfairTryAcquireShared(int acquires) {
    for(;;) {
        int available = getState();
        int remaining = available-acquires;
        if(remaining <0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

release() releases the lock. This method calls AQS's tryReleaseShared method, and then implements this method in its own NonfairSync.

protected final boolean tryReleaseShared(int releases) {
    for(;;) {
        int current = getState();
        int next = current + releases;
        if(next <current) //overflow
            throw new Error("Maximum permit count exceeded");
        if(compareAndSetState(current, next))
            return true;
    }
}

If the semaphore is initialized by new Semaphore(1), this is equivalent to an exclusive lock, which is similar to ReentrantLock.

CountDownLatch

CountDownLatch(thread counter):a thread waits for other threads to execute before executing multiple threads together. Use scenario:The main thread depends on the data of other child threads. Asynchronous to synchronous operation.

Example:Calculate the sum of the data returned by the Ali interface and the data returned by the Tencent interface. code show as below

public static void main(String []args) throws Exception {
    CountDownLatch countDownLatch = new CountDownLatch(2);
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 5, TimeUnit.SECONDS, new ArrayBlockingQueue <>(10), new ThreadNameFactory());
    final Integer []ints = new Integer [2];
    threadPoolExecutor.execute(()-> {
        try {
            System.out.println("Request Ali interface");
            Thread.sleep(2000);
            System.out.println("Request Ali interface to end");
            ints [0]= 10;
        } catch(InterruptedException e) {
            e.printStackTrace();
        } finally {
            countDownLatch.countDown();
        }
    });
    threadPoolExecutor.execute(()-> {
        try {
            System.out.println("Request Tencent Interface");
            Thread.sleep(3000);
            System.out.println("Request the end of Tencent interface");
            ints [1]= 20;
        } catch(InterruptedException e) {
            e.printStackTrace();
        } finally {
            countDownLatch.countDown();
        }
    });
    System.out.println("Waiting for two interface requests to finish");
    countDownLatch.await();
    System.out.println("Both interfaces have been requested");
    int result = ints [0]+ ints [1];
    System.out.println(result);
    threadPoolExecutor.shutdown();
}

new CountDownLatch(int count) When several sub-threads are needed to join the main thread, they will be passed. If there are more passes, the main thread will always wait. CountDownLatch is also implemented through AQS. The new CountDownLatch(int count) initialization method sets the initial value of state in AQS. Each time the countDown() method is called, the state will be reduced by 1. When the main thread calls the await() method, it will wait for state to equal 0 will continue to execute.

to sum up

Shared locks allow multiple threads to acquire locks simultaneously and access shared resources concurrently, improving efficiency.