各种锁的理解

HeJin大约 3 分钟JavaJUC并发编程

公平锁和非公平锁

  • 公平锁:非常公平。不能插队,必须先来后到。
  • 非公平锁:非常不公平。可以插队。(默认非公平锁)
public ReentrantLock() {
    sync = new NonfairSync();
}   
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

可重入锁

也叫递归锁。拿到了外面的锁之后,自动获得里面的锁。

synchronized实现

public class Demo01 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sms();
        },"A").start();
        new Thread(()->{
            phone.sms();
        },"B").start();
    }
}
class Phone{
    public synchronized void sms(){
        System.out.println(Thread.currentThread().getName() + " sms");
        // 这里也有锁
        call();
    }
    public synchronized void call(){
        System.out.println(Thread.currentThread().getName() + " call");
    }
}

结果:

A sms
A call
B sms
B call
    
Process finished with exit code 0

Lock锁实现:有两把锁

public class Demo02 {
    public static void main(String[] args) {
        Phone2 phone = new Phone2();
        new Thread(()->{
            phone.sms();
        },"A").start();
        new Thread(()->{
            phone.sms();
        },"B").start();
    }
}
class Phone2{
    Lock lock = new ReentrantLock();
    public void sms(){
        // 第一把锁
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " sms");
            // 第二把锁
            call();
        } finally {
            lock.unlock();
        }
    }
    public void call(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " call");
        } finally {
            lock.unlock();
        }
    }
}

结果:

A sms
A call
B sms
B call
    
Process finished with exit code 0

lock和unlock必须要配对,不然会发生死锁。

public class Demo02 {
    public static void main(String[] args) {
        Phone2 phone = new Phone2();
        new Thread(()->{
            phone.sms();
        },"A").start();
        new Thread(()->{
            phone.sms();
        },"B").start();
    }
}
class Phone2{
    Lock lock = new ReentrantLock();
    public void sms(){
        lock.lock();
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " sms");
            call();
        } finally {
            lock.unlock();
        }
    }
    public void call(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " call");
        } finally {
            lock.unlock();
        }
    }
}

结果:程序卡住。 image-20210325164008540

必须配对。

public class Demo02 {
    public static void main(String[] args) {
        Phone2 phone = new Phone2();
        new Thread(()->{
            phone.sms();
        },"A").start();
        new Thread(()->{
            phone.sms();
        },"B").start();
    }
}
class Phone2{
    Lock lock = new ReentrantLock();
    public void sms(){
        lock.lock();
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " sms");
            call();
        } finally {
            lock.unlock();
            lock.unlock();
        }
    }
    public void call(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " call");
        } finally {
            lock.unlock();
        }
    }
}

结果:

A sms
A call
B sms
B call
    
Process finished with exit code 0

自旋锁

image-20210325163913704
image-20210325163913704
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    return var5;
}

自定义自旋锁

/**
 * @Desc 自旋锁
 * @Author HeJin
 * @Date 2021/3/13 15:03
 */
public class SpinLock {
    AtomicReference<Thread> atomicReference = new AtomicReference<>();
    /**
     * 加锁
     */
    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName() + "===> myLock");
        // 自旋锁
        while (!atomicReference.compareAndSet(null, thread)){
        }
    }
    /**
     * 解锁
     */
    public void myUnLock(){
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName() + "===> myUnLock");
        atomicReference.compareAndSet(thread, null);
    }
}

测试

public class TestSpinLock {
    public static void main(String[] args) throws InterruptedException {
        // 底层使用自旋锁CAS实现的
        SpinLock lock = new SpinLock();
        new Thread(() -> {
            lock.myLock();
            try{
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.myUnLock();
            }
        },"T1").start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
            lock.myLock();
            try{
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.myUnLock();
            }
        },"T2").start();
    }
}

结果:

T1===> myLock
T2===> myLock
T1===> myUnLock
T2===> myUnLock
    
Process finished with exit code 0

T1加锁后,T2进入自旋,只有T1解锁后,T2才能加锁成功。

死锁排查

死锁:多个线程互相抱着对方的资源,形成僵持。多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行。从而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有两个以上对象的锁时,就可能会发生死锁的问题。

image-20210325163833622
image-20210325163833622

产生死锁的四个必要条件

  • 互斥条件:一个资源每次只能被一个进程使用。
  • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

上面列出了死锁的四个必要条件,我们只要想办法破坏其中的任意一个或多个条件就可以避免死锁发生。

public class DeadLockDemo {
    public static void main(String[] args) {
        String lockA = "A";
        String lockB = "B";
        new Thread(new MyThead(lockA, lockB), "T1").start();
        new Thread(new MyThead(lockB, lockA), "T2").start();
    }
}
class MyThead implements Runnable{
    private String lockA;
    private String lockB;
    public MyThead(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);
            }
        }
    }
}

结果: image-20210325163746383

解决办法

  • 1、使用jps定位进程号。
image-20210325163722319
image-20210325163722319
  • 2、使用jstack 进程号查看堆栈信息
image-20210325163658684
image-20210325163658684

面试,工作中排查问题。

  • 日志
  • 堆栈