JUC concurrent programming -- understanding of various locks

Posted by dmcglone on Thu, 30 Sep 2021 06:40:05 +0200

JUC concurrent programming -- understanding of various locks

1. Fair lock

Fair lock means that when the lock is available, the thread waiting on the lock for the longest time will obtain the right to use the lock and must come first.

//ReentrantLock(true) is set to fair lock
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

Unfair locks are randomly assigned this right of use and can jump the queue. Sometimes, some threads take a long time to execute. If other threads need locks, they need to wait a long time. Therefore, in order to provide efficiency, we generally use unfair locks

//ReentrantLock() is a non fair lock by default
public ReentrantLock() {
    sync = new NonfairSync();
}

2. Reentrant lock

Reentrant lock is also called recursive lock, which means that when the same thread obtains the lock in the outer method, it will automatically obtain the lock in the inner method. Basically, reentrant locks are used in JDK to avoid deadlock.

synchronized reentrant lock

public class Demo01 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sms();
        },"A").start();
}

class Phone{
    public synchronized void sms(){
        System.out.println(Thread.currentThread().getName()+"=> sms");
        call();//Inner layer method
    }
    //Lock of inner method
    public synchronized void call(){
        System.out.println(Thread.currentThread().getName()+"=> call");
    }
}


Lock reentrant lock

public class Demo02 {

    public static void main(String[] args) {
        Phone2 phone = new Phone2();
        new Thread(()->{
            phone.sms();
        },"A").start();
}
class Phone2{

    Lock lock=new ReentrantLock();

    public void sms(){
        lock.lock(); //Details: These are two locks, sms and call
        //lock locks must be paired and must be released several times after being added, otherwise they will be locked inside
        try {
            System.out.println(Thread.currentThread().getName()+"=> sms");
            call();//There is also a lock here
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    
    public void call(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "=> call");
        }catch (Exception e){
            e.printStackTrace();
        }
        finally {
            lock.unlock();
        }
    }
}


3. Spin lock

Spin lock: when a thread is acquiring a lock, if the lock has been acquired by other threads, the thread will wait in a loop, and then constantly judge whether the lock can be successfully acquired, and will not exit the loop until the lock is acquired. The thread trying to acquire the lock will not be blocked immediately. Try to acquire the lock in a circular way! Reduce context switching! Disadvantages: CPU consumption

Custom spin lock

public class SpinlockDemo {
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    //Lock
    public void myLock() {
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "-->myLock");
        while (!atomicReference.compareAndSet(null, thread)) {

        }
    }
    //Unlock
    public void myUnLock() {
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "-->myUnlock");
        atomicReference.compareAndSet(thread,null);
    }
}

Test spin lock

public class TestSpinLock {
    public static void main(String[] args) throws InterruptedException {
        
        SpinLockDemo lockDemo = new SpinLockDemo();
        new Thread(()->{
            lockDemo.myLock();
            try{
                TimeUnit.SECONDS.sleep(5);
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                lockDemo.myUnlock();
            }

        },"T1").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(()->{
            lockDemo.myLock();
            try{
                TimeUnit.SECONDS.sleep(1);
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                lockDemo.myUnlock();
            }
        },"T2").start();
    }
}

Thread T1 gets the lock first, and T2 waits for spin. T2 does not end spin until T1 releases the lock and obtains the lock.

4. Deadlock

Deadlock problem: multiple threads hold each other's required resources, and then form a deadlock. After a deadlock occurs, there will be no exception or prompt, but all threads are blocked and cannot continue.

Four necessary conditions for deadlock generation

  • Mutually exclusive condition: a resource can only be used by one process at a time
  • Request and hold condition: when a process is blocked by requesting resources, it will hold the obtained resources
  • Conditions of non deprivation: the resources obtained by the process cannot be forcibly deprived before they are used up
  • Circular waiting condition: a circular waiting resource relationship is formed between several processes

Deadlock Case

import java.util.concurrent.TimeUnit;

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

        String lockA = "lockA";
        String lockB = "lockB";
        new Thread(new MyThread(lockA,lockB),"T1").start();
        new Thread(new MyThread(lockB,lockA),"T2").start();
    }
}

class MyThread implements Runnable{
    private String lockA;
    private String lockB;

    public MyThread(String lockA, String lockB){
        this.lockA = lockA;
        this.lockB = lockB;
    }

    @Override
    public void run() {
        synchronized (lockA){
            System.out.println(Thread.currentThread().getName()+ " lock:"+lockA + " => get" + lockB);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB){
                System.out.println(Thread.currentThread().getName()+ " lock:"+lockB + " => get" + lockA);

            }
        }
    }
}

Operation results:

Thread T1 has occupied lock A and is waiting for lock B. thread T2 has occupied lock B and is waiting for lock A. the two threads are deadlocked with each other, resulting in program blocking.

How to troubleshoot deadlock

Use for troubleshooting,

jps(Java Virtual Machine Process Status Tool) is a small tool provided by jdk to view the current java process.

Take the deadlock above as an example for troubleshooting:

1. Use jps -l to locate the process number

2. Use jstack + process number to view stack information to analyze threads

Topics: Java lock