1 Defects of synchronized

Synchronized is a keyword in Java, that is, a built-in feature of the Java language. So why Lock?

In the previous article, we learned that if a block of code is modified by synchronized, when a thread acquires the lock and executes the block, the other threads have to wait until the thread that acquired the lock releases the lock. The thread that acquired the lock releases the lock only in two cases:

1) The thread that acquires the lock completes the code block and then releases the lock;

2) When thread execution is abnormal, the JVM causes the thread to release the lock automatically.

So if the thread that acquired the lock is blocked because it is waiting for an IO or some other reason (such as calling a sleep method), but does not release the lock, the other thread has to wait for nothing. Imagine how inefficient this can be.

Therefore, there needs to be a mechanism to prevent waiting threads from waiting indefinitely (such as waiting for a certain amount of time or being able to respond to interrupts), and this can be done with a Lock.

Another example: When multiple threads read and write files, the read and write operations conflict, and the write and write operations conflict, but the read and read operations do not conflict.

But using the synchronized keyword causes a problem:

If multiple threads are only reading, while one thread is reading, the other threads can only wait and cannot read.

So you need a mechanism that allows multiple threads to just read without conflicting threads, and you can do that with a Lock.

In addition, the Lock lets you know if the thread successfully acquired the Lock. Synchronized can’t do this.

In summary, this means that Lock offers more functionality than synchronized. But be aware of the following:

1) Lock is not built into the Java language, and synchronized is a Java keyword and therefore a built-in feature. Lock is a class through which synchronous access is implemented;

2) There is a big difference between Lock and synchronized. Using synchronized does not require users to release the Lock manually. After the execution of synchronized method or synchronized code block, the system will automatically let the thread release the Lock. A Lock must be manually released by the user. If the Lock is not actively released, a deadlock may occur.

2 Lock

2.1 already

public class LockTest {
    public static void main(String[] args) {

        new LockTest().init();
    }

    private void init(a) {
        final Outputer outputer = new Outputer();

        new Thread(new Runnable() {
            @Override
            public void run(a) {

                while (true) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    outputer.output("kpioneer");

                }
            }
        }).start();


        new Thread(new Runnable() {
            @Override
            public void run(a) {

                while (true) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    outputer.output("Jack");

                }
            }
        }).start();
    }

    static class Outputer {
        
        Lock lock = new ReentrantLock();

        public void output(String name) {
            int len = name.length();
            lock.lock();
            /** ** Release the lock regardless of whether the program is running properly or not * or you will never enter the page */
            try {
                for (int i = 0; i < len; i++) {
                    System.out.print(name.charAt(i));
                }
                System.out.println();
            } finally{ lock.unlock(); }}}}Copy the code

Kpioneer Jack Kpioneer Jack Kpioneer Jack Jack kpioneer JackCopy the code
2.2 ReentrantReadWriteLock

There is a scenario in which read and write operations are performed on shared resources, and the write operations are not as frequent as the read operations. There is no problem with multiple threads reading a resource simultaneously when there is no write operation, so multiple threads should be allowed to read a shared resource simultaneously. However, if one thread wants to write to a shared resource, no other thread should be allowed to read or write to the resource.

For this scenario, JAVA provides ReentrantReadWriteLock, a read and write lock. ReentrantReadWriteLock is a shared lock related to read operations. One is a write-related lock, called an exclusive lock, described as follows:

Prerequisites for a thread to enter a read lock:

There are no write locks for other threads,

There is no write request or there is a write request, but the calling thread is the same as the thread holding the lock.

A thread can enter a write lock if:

There are no read locks for other threads

There are no write locks for other threads

Read/write locks have three important features:

(1) Fair selection: support unfair (default) and fair lock acquisition methods, throughput is still unfair is better than fair.

(2) Re-entry: Both read and write locks support thread re-entry.

(3) Lock degradation: a write lock can be degraded to a read lock by following the sequence of obtaining a write lock, obtaining a read lock, and then releasing the write lock.


public class ReadWriteLockTest {
    public static void main(String[] args) {

        final Queue q = new Queue();

        for (int i = 0; i < 3; i++) {

            new Thread() {
                @Override
                public void run(a) {

                    while (true) {
                        q.get();
                    }
                }
            }.start();

            new Thread() {
                @Override
                public void run(a) {
                    while (true) {
                        q.put(new Random().nextInt(10000)); } } }.start(); }}}class Queue {

    // Share data. Only one thread can write the data, but multiple threads can read the data simultaneously.
    ReadWriteLock rwl = new ReentrantReadWriteLock();
    private Object data = null;// Share data. Only one thread can write data, but more than one thread can read data

    public void get(a) {
        // The thread can only read but not write
        rwl.readLock().lock();
        try {

            System.out.println(Thread.currentThread().getName() + " be ready to read data!");
            Thread.sleep((long) (Math.random() * 1000));
            System.out.println(Thread.currentThread().getName() + " have read data :" + data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally{ rwl.readLock().unlock(); }}public void put(Object data) {
        // Write to lock, do not allow other threads to read and write
        rwl.writeLock().lock();

        try {
            System.out.println(Thread.currentThread().getName() + " be ready to write data!");

            Thread.sleep((long) (Math.random() * 1000));

            this.data = data;
            System.out.println(Thread.currentThread().getName() + " have write data: " + data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally{ rwl.writeLock().unlock(); }}}Copy the code

Thread-0 be ready to read data! Thread-2 be ready to read data! Thread-2 have read data :null Thread-0 have read data :null Thread-1 be ready to write data! Thread-1 have write data: 4664 Thread-1 be ready to write data! Thread-1 have write data: 1849 Thread-3 be ready to write data! Thread-3 have write data: 75 Thread-3 be ready to write data! Thread-3 have write data: 8222 Thread-3 be ready to write data! Thread-3 have write data: 3056 Thread-3 be ready to write data! Thread-3 have write data: 6114 Thread-3 be ready to write data! Thread-3 have write data: 6376 Omitted...Copy the code

3 Introduces concepts related to locking

Having introduced the basic use of Lock earlier, this section introduces some concepts related to locking.

3.1 Reentrant lock

If a lock is reentrant, it is called a reentrant lock. Reentrant locks like synchronized and ReentrantLock are reentrant locks, and ReentrantLock in my opinion actually indicates the lock allocation mechanism: thread-based allocation, not method-call-based allocation. As a simple example, when a thread executes a synchronized method, such as method1, and in method1 another synchronized method, method2, is called, the thread doesn’t have to re-apply for the lock. Instead, it can execute method2 directly.

Take a look at the following code:


class MyClass {
    public synchronized void method1(a) {
        method2();
    }
     
    public synchronized void method2(a) {}}Copy the code

The two methods in the code above, method1 and method2, are both modified with synchronized. If thread A executes method1 at some point, thread A acquires the lock on this object, and since method2 is A synchronized method, If synchronized is not reentrant, thread A needs to reapply for the lock. This can cause A problem, however, because thread A already holds the lock on the object and is requesting the lock on the object, so thread A is waiting for A lock that will never be acquired.

Synchronized and Lock are both reentrant, so this doesn’t happen.

3.2 Interruptible locking

Interruptible lock: As the name suggests, a lock that can be broken accordingly. In Java, synchronized is not a breakable Lock and Lock is a breakable Lock. If thread A is executing the code in the lock, and thread B is waiting to acquire the lock, maybe because the waiting time is too long, thread B doesn’t want to wait and wants to do other things first, we can tell it to interrupt itself or interrupt it in another thread, this is called interruptible lock. The use of lockInterruptibly() was demonstrated earlier.

3.3 fair lock

Fair locking means that locks are acquired in the order in which they are requested. For example, if there are multiple threads waiting for a lock, when the lock is released, the thread that waited the longest (the thread that requested it first) gets the lock. This is a fair lock.

An unfair lock is one in which there is no guarantee that the lock is acquired in the order in which it was requested. This may result in one or more threads never acquiring the lock.

In Java, synchronized is an unfair lock that does not guarantee the order in which waiting threads acquire the lock.

For ReentrantLock and ReentrantReadWriteLock, it is an unfair lock by default, but can be set to a fair lock.

Take a look at the source code for these two classes:

**

   /** * Base of synchronization control for this lock. Subclassed * into fair and nonfair versions below. Uses AQS state to * represent the number of holds on the lock. */
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        /**
         * Performs {@link Lock#lock}. The main reason for subclassing
         * is to allow fast path for nonfair version.
         */
        abstract void lock(a);

        /** * Performs non-fair tryLock. tryAcquire is implemented in * subclasses, but both need nonfair try for trylock method. */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true; }}else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if(Thread.currentThread() ! = getExclusiveOwnerThread())throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

        protected final boolean isHeldExclusively(a) {
            // While we must in general read state before owner,
            // we don't need to do so to check if current thread is owner
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        final ConditionObject newCondition(a) {
            return new ConditionObject();
        }

        // Methods relayed from outer class

        final Thread getOwner(a) {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }

        final int getHoldCount(a) {
            return isHeldExclusively() ? getState() : 0;
        }

        final boolean isLocked(a) {
            returngetState() ! =0;
        }

        /** * Reconstitutes the instance from a stream (that is, deserializes it). */
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state}}Copy the code

    /** * Sync object for non-fair locks */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /** * Performs lock. Try immediate barge, backing up to normal * acquire on failure. */
        final void lock(a) {
            if (compareAndSetState(0.1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            returnnonfairTryAcquire(acquires); }}/** * Sync object for fair locks */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock(a) {
            acquire(1);
        }

        /** * Fair version of tryAcquire. Don't grant access unless * recursive call or no waiters or is first. */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if(! hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true; }}else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false; }}Copy the code

In addition, there are many methods defined in the ReentrantLock class, such as:

IsFair () // determine whether the lock isFair

IsLocked () // Checks whether the lock has been acquired by any thread

IsHeldByCurrentThread () // Determines whether the lock is acquired by the current thread

HasQueuedThreads () // Determines whether any threads are waiting for the lock

There is a similar method in ReentrantReadWriteLock, which can also be set to fair and unfair locks. Remember, however, that ReentrantReadWriteLock does not implement the Lock interface. It implements the ReadWriteLock interface.

3.4 read-write lock

Read/write locks divide access to a resource (such as a file) into two locks, a read lock and a write lock.

Because of the read-write lock, the read operation between multiple threads does not conflict.

ReadWriteLock is a read/write lock. It is an interface. ReentrantReadWriteLock implements this interface.

Read locks can be obtained by readLock() and write locks by writeLock().


public class CacheDemo {
    private Map<String, Object> cache = new HashMap<>();
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public static void main(String[] args) {}public Object getData(String key) {

        Object value = null;
        // Enable read lock first, fetch from cache
        readWriteLock.readLock().lock();
        try {
            value = cache.get(key);
            // If the cache does not release the read lock, write the lock
            if (value == null) {
                / / corresponding queryDB ()
                readWriteLock.readLock().unlock();
                // A read lock must be unlocked to obtain a write lock
                readWriteLock.writeLock().lock();
                try {
                    / / corresponding queryDB ()
                    value = queryDB();
                } finally {
                    // Release the write lock
                    readWriteLock.writeLock().unlock();
                }
                // Then read the lockreadWriteLock.readLock().lock(); }}finally {
            // Finally release the read lock
            readWriteLock.readLock().unlock();
        }
        return value;

    }

    public Object queryDB(a) {
        return "aaaa"; }}Copy the code