JUC并发编程—— 各种锁的理解

小知识,大挑战!本文正在参与「程序员必备小知识」创作活动

本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。

1、公平锁,非公平锁

公平锁是指当锁可用时,在锁上等待时间最长的线程将获得锁的使用权,必须先来后到。

//ReentrantLock(true)设置为公平锁
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

复制代码

非公平锁则随机分配这种使用权,可以插队。有时候,一些线程的执行时间很长,如果其他线程需要锁的话就需要等待很长的时间,所以为了提供效率,我们一般使用非公平锁

//ReentrantLock()默认是非公平锁
public ReentrantLock() {
    sync = new NonfairSync();
}
复制代码

2、可重入锁

可重入锁又叫递归锁,指同一个线程在外层方法获取锁的时候,进入内层方法会自动获取锁。JDK 中基本都是可重入锁,避免死锁的发生。

synchronized 可重入锁

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();//内层方法
    }
    //内层方法的锁
    public synchronized void call(){
        System.out.println(Thread.currentThread().getName()+"=> call");
    }
}


复制代码

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(); //细节:这个是两把锁,分别是sms和call
        //lock锁必须配对,加了几次就要释放几次,否则就会死锁在里面
        try {
            System.out.println(Thread.currentThread().getName()+"=> sms");
            call();//这里也有一把锁
        } 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、自旋锁

自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。尝试获取锁的线程不会立即阻塞,采用循环的方式尝试获取锁!减少上下文的切换!缺点会消耗CPU

自定义自旋锁

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

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

        }
    }
    //解锁
    public void myUnLock() {
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "-->myUnlock");
        atomicReference.compareAndSet(thread,null);
    }
}
复制代码

测试自旋锁

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();
    }
}
复制代码

线程T1先拿到锁,T2在自旋等待,直到T1释放了锁,T2才结束自旋,获取到锁。

4、死锁

死锁问题:多个线程互相拿着对方需要的资源,然后形成僵持。出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。

产生死锁的四个必要条件

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

死锁案例

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);

            }
        }
    }
}

复制代码

运行结果:

在这里插入图片描述

线程T1已经占有了A锁,在等待B锁,线程T2已经占有了B锁,在等待A锁,两个线程互相僵持,导致程序运行阻塞。

如何排查死锁

使用 进行排查,

jps(Java Virtual Machine Process Status Tool)是 jdk 提供的一个查看当前java进程的小工具。

以上面死锁为例,进行排查:

1、使用 jps -l 定位进程号

在这里插入图片描述

2、使用jstack + 进程号查看堆栈信息,用以分析线程情况

在这里插入图片描述

猜你喜欢

转载自juejin.im/post/7017439808624541709
今日推荐