Implement a synchronizer based on AQS

As mentioned above, we can try to implement a synchronizer ourselves. We can simply refer to the implementation of ReentrantLock class, and we can simply implement a non reentrant exclusive lock!

 

1, A simple analysis of the structure of ReentrantLock

As shown in the figure below, the Lock interface is implemented directly, and then an internal class is defined to inherit AQS, regardless of fair Lock and unfair Lock. As I said earlier when AQS is mentioned, tryAcquire and tryRelease are two methods that are implemented in specific subclasses according to the actual situation. It can be imagined that the internal class mainly implements tryAcquire and tryRelease;

 

 

Let's take a look at the Lock interface, these methods are what we need to implement, mainly to obtain and release locks, and there is also a method to implement conditional variables;

Note here that some methods are followed by the word "interruptible". This method means that if the thread is suspended in the blocking queue, another thread will call the thread's interrupt method, and an exception will be thrown immediately. Without interruptible, the thread will not respond to the interrupt!

 

 

If we look at the implementation of lock, unlock and other methods in ReentrantLock, we can know that they are all called Sync methods, that is, some methods in AQS. So here we can regard Sync as a tool class. We mainly use these methods of lock interface to realize our lock function;

 

 

 

 

2, Create a lock MyNonLock

We only need to create a class to implement the Lock class, and then there is an inner class MySync inheriting AQS in this class, and then we call some methods of MySync objects in those implementations of Lock.

package com.example.demo.Lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class MyNonLock implements Lock, java.io.Serializable {
    
    //Create a specific MySync To do specific work
    private final MySync mySync = new MySync();

    @Override
    public void lock() {
        mySync.acquire(1);
    }

    @Override
    public boolean tryLock() {
        return mySync.tryAcquire(1);
    }
    
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return mySync.tryAcquireNanos(1, unit.toNanos(time));
        
    }
    
    //With Interruptibly When a thread is suspended in a blocking queue,
    //Other threads call the thread's interrupt method to interrupt the thread, and the thread will throw InterruptedException abnormal
    @Override
    public void lockInterruptibly() throws InterruptedException {
         mySync.acquireInterruptibly(1);
    }

    @Override
    public void unlock() {
        mySync.release(1);
    }

    //Very convenient to obtain conditional variables
    @Override
    public Condition newCondition() {
        return mySync.newCondition();
    }
    
    

    private static class MySync extends AbstractQueuedSynchronizer {

        // Whether the lock has been held
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        // If state 0, try to acquire the lock, and state Modified to 1
        public boolean tryAcquire(int acquires) {
            assert acquires == 1;
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        // Attempt to release the lock state Set to 0
        protected boolean tryRelease(int releases) {
            assert releases == 1;
            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        //Provide condition variable interface
        Condition newCondition() {
            return new ConditionObject();
        }
    }

}

 

 

3, Producer consumer model

We can also implement the producer consumer mode according to our own lock MyNonLock. Note that this lock is non reentrant and does not need to record the number of times the thread holding the lock acquires the lock. A state value of 0 indicates that the current lock is not occupied, and a state value of 1 indicates that it has been occupied;

package com.example.demo.study;

import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.Condition;

import com.example.demo.Lock.MyNonLock;

public class Study0202 {
    // Let's add a string to this queue
    final static Queue<String> queue = new LinkedBlockingQueue<String>();
    // Create our own lock object
    final static MyNonLock lock = new MyNonLock();
    // Queue queue When the string in is full, other production threads will be dropped into the condition queue
    final static Condition full = lock.newCondition();
    // Queue queue It is empty, and the rest of the consuming threads will be put into the condition queue
    final static Condition empty = lock.newCondition();
    // queue queue The maximum number of stored strings can be 3
    final static int queue_MAX_SIZE = 3;

    //Queue queue Press in string
    public static void add() {
        lock.lock();
        try {
            // When the queue is full, drop other production threads into the full In the condition queue of
            while (queue.size() == queue_MAX_SIZE) {
                full.await();
            }
            System.out.println("prd:" + "hello");
            // Queue queue Add string to
            queue.add("hello");
            // Production success, wake up all threads in the consumption condition queue to consume
            empty.signalAll();
        } catch (Exception e) {
            //
        } finally {
            lock.unlock();
        }
    }

    //Queue from queue Pop up string
    public static void poll() {
        lock.lock();
        try {
            // Queue queue If there is no string in, the remaining consuming threads will be thrown into the enpty In the corresponding queue
            while (queue.size() == 0) {
                empty.await();
            }
            // Consumption queue queue String in
            String poll = queue.poll();
            System.out.println("consumer:" + poll);
            // Wake up after successful consumption full All production threads in to produce strings
            full.signalAll();
        } catch (Exception e) {
            //
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        // Producer thread
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                add();
            }).start();
        }

        // Consumer thread
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                poll();
            }).start();
        }
    }
}

 

 

You can see that there are only 3 strings in the queue at most, and they can be consumed finally!

Tags: Java REST

Posted on Mon, 03 Feb 2020 23:58:39 -0500 by williamg