Java in-depth learning: locks

Keywords: Java SQL jvm

Re-lockable:

Simply put, repetitive locking is supported and reusable

Features: Locks can be passed, methods can be passed recursively

Objective: To avoid deadlock phenomenon.

Code:

public class Test implements Runnable {

    @Override
    public void run() {
        method1();
    }

    public synchronized void method1() {
        System.out.println("method1");
        method2();
    }

    public synchronized void method2() {
        System.out.println("method2");
    }

    public static void main(String[] args) {
        new Thread(new Test()).start();
    }

}

Print:

method1
method2

Analysis: If the lock is not reusable, there will be a deadlock problem.

 

Use the ReentrantLock lock:

public class TestLock implements Runnable {
    //Reentrant lock
    private Lock reentrantLock = new ReentrantLock();

    @Override
    public void run() {
        method1();
    }

    public void method1() {
        try {
            reentrantLock.lock();
            System.out.println("method1");
            method2();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock();
        }
    }

    public void method2() {
        try {
            reentrantLock.lock();
            System.out.println("method2");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock();
        }
    }

    public static void main(String[] args) {
        new Thread(new TestLock()).start();
    }

}

 

Read write lock:

When concurrency is high, read operations should not be allowed at the same time as write operations.

(in multithreading, read-write and write write cannot coexist.)

Code: Thread security issues in manufacturing read and write operations

public class TestWriteLock {

    Map<String, String> cache = new HashMap<>();

    //Write element
    public void put(String key, String value) {
        try {
            System.out.println("Start writing key : " + key + " value : " + value);
            Thread.sleep(50);
            cache.put(key, value);
            System.out.println("Write completed key : " + key + " value : " + value);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //Read element
    public String get(String key) {
        System.out.println("Start reading key : " + key);
        String value = cache.get(key);
        System.out.println("Read successful key : " + key + " value : " + value);
        return value;
    }

    public static void main(String[] args) {
        TestWriteLock test = new TestWriteLock();
        Thread readThread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    test.put("i", i + "");
                }
            }
        });

        Thread writeThread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    test.get("i");
                }
            }
        });

        readThread.start();
        writeThread.start();
    }
}

Observing Printing: Discovering Unreasonable

Start writing key: I value: 0
Start reading key:i
Read successful key: I value: null
.................................

Analysis: When the writing is not completed, the reader starts to read, and the result is empty.

 

Solve:

1. Use synchronized, although it can be solved, but inefficient, write operations can not read at the same time, resulting in blocking.

2. Use read-write locks

public class TestWriteLock {

    Map<String, String> cache = new HashMap<>();

    ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
    ReentrantReadWriteLock.ReadLock readLock = lock.readLock();

    //Write element
    public void put(String key, String value) {
        try {
            writeLock.lock();
            System.out.println("Start writing key : " + key + " value : " + value);
            Thread.sleep(50);
            cache.put(key, value);
            System.out.println("Write completed key : " + key + " value : " + value);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            writeLock.unlock();
        }
    }

    //Read element
    public String get(String key) {
        String value = "";
        try {
            readLock.lock();
            System.out.println("Start reading key : " + key);
            value = cache.get(key);
            System.out.println("Read successful key : " + key + " value : " + value);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readLock.unlock();
        }
        return value;
    }

    public static void main(String[] args) {
        TestWriteLock test = new TestWriteLock();
        Thread readThread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    test.put("i", i + "");
                }
            }
        });

        Thread writeThread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    test.get("i");
                }
            }
        });
        readThread.start();
        writeThread.start();
    }
}

Observation Printing: Perfect Solution

 

Optimistic lock:

Simply put, optimistic locks are no locks, no blockages, no waiting

An SQL statement demonstrates:

UPDATE TABLE SET X=X+1,VERSION=VERSION+1 WHERE ID=#{id} AND VERSION=#{version}

In the case of high concurrency, assuming that the initial version is 1, request 1 arrives and can be found according to id and version, so updates are allowed

Request 2 operates at the same time, but cannot be found by id and version (modified by Request 1), so updates are not allowed.

 

Pessimistic lock:

Simply put, heavy locks block and wait.

It can be understood that only one thread is allowed to operate after locking, that is, synchronized in Java.

 

Atomic class:

A piece of code that simulates thread security issues:

public class ThreadTest implements Runnable {

    private static int count = 1;

    @Override
    public void run() {
        while (true) {
            Integer count = getCount();
            if (count >= 100) {
                break;
            }
            System.out.println(count);
        }
    }

    public synchronized Integer getCount() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return count++;
    }

    public static void main(String[] args) {
        ThreadTest t = new ThreadTest();
        new Thread(t).start();
        new Thread(t).start();
    }

}

After observing the printing, we found that there was indeed a thread security problem.

 

A Modification Method: Low Efficiency

    public synchronized Integer getCount() {

 

Using Atomic Class: Optimistic Lock, No Lock at the Bottom Level, Using CAS Lock-Free Technology

public class ThreadTest implements Runnable {

    // Thread safety
    private AtomicInteger atomicInteger = new AtomicInteger();

    @Override
    public void run() {
        while (true) {
            Integer count = getCount();
            if (count >= 100) {
                break;
            }
            System.out.println(count);
        }
    }

    public Integer getCount() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return atomicInteger.incrementAndGet();
    }

    public static void main(String[] args) {
        ThreadTest t = new ThreadTest(); 
        new Thread(t).start();
        new Thread(t).start();
    }

}

 

CAS lock-free technology (Compare And Swap):

Comparisons and exchanges

Store copies of shared memory in local memory

For example, i=0 in main memory is copied to the local memory of two threads.

Two threads execute i++, local memory becomes i=1, and then refresh into main memory

 

CAS algorithm:

It contains three parameters CAS(V,E,N):

V represents the variable to be updated (main memory)

E represents the expected value (local memory)

N represents a new value (new value)

The value of V is set to N only if the value of V equals the value of E (main memory = local memory).

If the value of V is different from that of E (main memory! = local memory), it means that other threads have been updated, and the current thread does nothing.

Finally, CAS returns the true value of the current V.

 

Observe the source code of the atomic class:

    /** 
     * Atomically increments by one the current value. 
     * 
     * @return the updated value 
     */  
    public final int incrementAndGet() {  
        for (;;) {  
            //Get the current value  
            int current = get();  
            //Set expectations  
            int next = current + 1;  
            //call Native Method compareAndSet,implement CAS operation  
            if (compareAndSet(current, next))  
                //The expected value is returned after success, otherwise the wireless loop  
                return next;  
        }  
    }  

 

Disadvantages of CAS lock-free mechanism:

1. dead cycle

2.ABA Question: If the variable V is A when it is first read and checks that it is still A when it is ready for assignment, does that mean that its value has not been modified by other threads?

(If it was changed to B and then back to A during this period, the CAS operation would mistakenly assume that it has never been modified. In response to this situation, the java Concurrent package provides a tagged atomic reference class, AtomicStampedReference, which can ensure the correctness of CAS by controlling the version of variable values.)

 

JVM Data Synchronization: Using Distributed Lock

Posted by sentient0169 on Sat, 12 Oct 2019 02:54:36 -0700