三、Java锁

目录

1、乐观锁和悲观锁

1.1 悲观锁

1.2 乐观锁

2、java synchronized 8种情况

2.1、一个资源类,ab两个线程访问 同步邮件方法 和 同步短信方法。先执行邮件,还是短信?(先邮件,后短信)

2.2、一个资源类,ab两个线程访问 同步邮件睡眠方法 和 同步短信方法。先执行邮件,还是短信?(先邮件,后短信)

 2.3、一个资源类,ab两个线程访问 同步邮件睡眠方法 和 普通hello方法。先执行邮件,还是hello?(先hello,后邮件)

2.4、两个资源类,ab两个线程访问 分别调用同步邮件睡眠方法 和 同步短信。先执行邮件,还是短信?(先短信,后邮件) 

2.5、一个资源类,ab两个线程访问 分别调用静态同步睡眠邮件方法和 静态同步短信方法。先执行邮件,还是短信?(先邮件,后短信)

3.6、两个资源类,ab两个线程访问 分别调用静态同步睡眠邮件方法和 静态同步短信方法。先执行邮件,还是短信?(先邮件,后短信)

2.7、一个资源类,ab两个线程访问 分别调用静态同步睡眠邮件方法 和 同步短信方法。先执行邮件,还是短信?(先短信,后邮件) 

2.8、两个资源类,ab两个线程访问 分别调用静态同步睡眠邮件方法 和 同步短信方法。先执行邮件,还是短信?(先短信,后邮件) 

2.9、总结 

3、字节码角度分析Synchronized实现

3.1 class 编译技巧

3.2 分析过程(分析上面LockSyncDemo类)

3.2.1 分析Synchronized同步代码块

3.2.2 Synchronized普通同步方法 

3.2.3 Synchronized静态同步方法

4、从底层(OpenJDK)看Synchronized锁是什么? 

4.1 管程概念

4.2 【面试题】大厂:为什么任何一个对象都可以成为一个锁?

5、公平锁和非公平锁

5.1 ReentrantLock抢票案例

5.2 非公平锁

5.3 公平锁

5.4 为什么又公平锁/非公平锁的设计?为什么默认是非公平锁? 

6、可重入锁(递归锁)

6.1 说明

6.2  可重入锁案例

6.2.1 隐式锁Synchronized

6.2.2 Synchronized可重入实现原理

6.2.3 显式锁Lock 

7、死锁及排查

7.1 死锁是什么?

7.2 死锁代码

7.3 何排查死锁? 

7.3.1 命令排查

7.3.2 jdk自带图形化

 8、写锁(独占锁)、读锁(共享锁)、自旋锁等其他锁后续会再继续


1、乐观锁和悲观锁

1.1 悲观锁

        认为自己在使用数据的时候一定有其他线程来修改数据,因此在获取数据的时候就会加上锁,确保数据不会被别的线程修改。

//=============悲观锁的调用方式
public synchronized void m1()
{
    //加锁后的业务逻辑......
}

// 保证多个线程使用的是同一个lock对象的前提下
ReentrantLock lock = new ReentrantLock();
public void m2() {
    lock.lock();
    try {
        // 操作同步资源
    }finally {
        lock.unlock();
    }
}

1.2 乐观锁

        认为自己在使用数据期间不会有任何线程修改数据,所以不会加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作。

        实现方式:CAS算法(可能出现ABA问题);可以加版号的CAS算法(防止ABA问题)。

2、java synchronized 8种情况

阿里巴巴代码规范

  • 【强制】高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
  • 说明:尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用 RPC 方法。

 通用类

class Phone{

    /** 同步邮件方法 */
    public synchronized void sendEmail(){
        System.out.println("-----sendEmail");
    }

    /** 同步短信方法 */
    public synchronized void sendSMS(){
        System.out.println("-----sendSMS");
    }

    /** 静态同步邮件方法 */
    @SneakyThrows
    public static synchronized void sendEmailStaticSleep(){
        TimeUnit.SECONDS.sleep(3);
        System.out.println("-----sendEmailStatic");
    }

    /** 静态同步短信方法 */
    public static synchronized void sendSMSStatic(){
        System.out.println("-----sendSMSStatic");
    }

    /** 同步邮件睡眠方法 */
    @SneakyThrows
    public synchronized void sendEmailSleep(){
        TimeUnit.SECONDS.sleep(3);
        System.out.println("-----sendEmailSleep");
    }

    /** 普通hello方法 */
    public void hello(){
        System.out.println("hello");
    }
}

2.1、一个资源类,ab两个线程访问 同步邮件方法 和 同步短信方法。先执行邮件,还是短信?(先邮件,后短信)

@SneakyThrows
public static void main(String[] args) {
    Phone phone = new Phone();
        new Thread(() -> {
        phone.sendEmail();
    },"a").start();
    // 就是为了让线程a先启动
    TimeUnit.MILLISECONDS.sleep(200);
    new Thread(() -> {
        phone.sendSMS();
    },"b").start();
}
    
-----sendEmail
-----sendSMS

2.2、一个资源类,ab两个线程访问 同步邮件睡眠方法 和 同步短信方法。先执行邮件,还是短信?(先邮件,后短信)

@SneakyThrows
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(() -> {
            phone.sendEmailSleep();
        },"a").start();
        // 就是为了让线程a先启动
        TimeUnit.MILLISECONDS.sleep(200);
        new Thread(() -> {
            phone.sendSMS();
        },"b").start();
  }
  
-----sendEmailSleep
-----sendSMS

对象锁

        一个对象里面如果有多个synchronized方法,某一时刻内,只要一个线程去调用其中的一个synchronized方法了,其他的线程都只能是等待,换句话说,某一个时刻内,只能有唯一的一个线程去访问这些synchronized方法,锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其他synchronized方法。

 2.3、一个资源类,ab两个线程访问 同步邮件睡眠方法 和 普通hello方法。先执行邮件,还是hello?(先hello,后邮件)

   @SneakyThrows
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(() -> {
            phone.sendEmailSleep();
        },"a").start();
        // 就是为了让线程a先启动
        TimeUnit.MILLISECONDS.sleep(200);
        
        new Thread(() -> {
            phone.hello();
        },"b").start();
  }
  
  hello
-----sendEmailSleep

hello并未和其他synchronized修饰的方法产生争抢

2.4、两个资源类,ab两个线程访问 分别调用同步邮件睡眠方法 和 同步短信。先执行邮件,还是短信?(先短信,后邮件) 

@SneakyThrows
    public static void main(String[] args) {
        Phone phone = new Phone();
        Phone phone2 = new Phone();
        
        new Thread(() -> {
            phone.sendEmailSleep();
        },"a").start();
        // 就是为了让线程a先启动
        TimeUnit.MILLISECONDS.sleep(200);
        new Thread(() -> {
            phone2.sendSMS();
        },"b").start();
        
     }

锁在两个不同的对象/两个不同的资源上,不产生竞争条件

2.5、一个资源类,ab两个线程访问 分别调用静态同步睡眠邮件方法和 静态同步短信方法。先执行邮件,还是短信?(先邮件,后短信)

 @SneakyThrows
    public static void main(String[] args) {
        Phone phone = new Phone();
        
        new Thread(() -> {
            // 这里会有红色的下划线,但是不影响运行
            phone.sendEmailStaticSleep();
        },"a").start();
        // 就是为了让线程a先启动
        TimeUnit.MILLISECONDS.sleep(200);
        
        new Thread(() -> {
            // 这里会有红色的下划线,但是不影响运行
            phone.sendSMSStatic();
        },"b").start();
        
     }
     
-----sendEmailStatic
-----sendSMSStatic

3.6、两个资源类,ab两个线程访问 分别调用静态同步睡眠邮件方法和 静态同步短信方法。先执行邮件,还是短信?(先邮件,后短信)

 @SneakyThrows
    public static void main(String[] args) {
        Phone phone = new Phone();
        Phone phone2 = new Phone();
        
        new Thread(() -> {
            phone.sendEmailStaticSleep();
        },"a").start();
        // 就是为了让线程a先启动
        TimeUnit.MILLISECONDS.sleep(200);
        
        new Thread(() -> {
            phone2.sendSMSStatic();
        },"b").start();
                
}
     
-----sendEmailStatic
-----sendSMSStatic

类锁

static + synchronized 是类锁 ,锁的是当前类的Class对象,phone 与phone2 这两个对象是通过Phone这个类建造出来的

2.7、一个资源类,ab两个线程访问 分别调用静态同步睡眠邮件方法 和 同步短信方法。先执行邮件,还是短信?(先短信,后邮件) 

@SneakyThrows
    public static void main(String[] args) {
       Phone phone = new Phone();
        
       new Thread(() -> {
            phone.sendEmailStaticSleep();
        },"a").start();
        // 就是为了让线程a先启动
        TimeUnit.MILLISECONDS.sleep(200);
        
        new Thread(() -> {
            phone.sendSMS();
        },"b").start();
                
}

-----sendSMS
-----sendEmailStatic

2.8、两个资源类,ab两个线程访问 分别调用静态同步睡眠邮件方法 和 同步短信方法。先执行邮件,还是短信?(先短信,后邮件) 

 @SneakyThrows
    public static void main(String[] args) {
        Phone phone = new Phone();
        Phone phone2 = new Phone();
        new Thread(() -> {
            phone.sendEmailStaticSleep();
        },"a").start();
        // 就是为了让线程a先启动
        TimeUnit.MILLISECONDS.sleep(200);
        
        new Thread(() -> {
            phone2.sendSMS();
        },"b").start();
                
}

-----sendSMS
-----sendEmailStatic

一个是对象锁,一个加的是类锁,不产生竞争条件

2.9 总结 

可以有两个理解

        非静态的同步方法,是对象锁(在堆中存在多个);静态同步方法是类锁(在方法区只存在一份);

        非静态的同步方法的监视器是当前对象(this);静态同步方法的监视器是当前类Class

synchronized 三种使用方法(体现)

  • 作用于实例方法,当前实例加锁,进入同步代码块前要获得当前实例的锁。
  • 作用于代码块,对括号里配置的对象加锁。
  • 作用于静态方法,当前类加锁,进去同步代码前要获得当前类对象的锁
public class LockSyncDemo {
    Object object = new Object();

    /**
     * 作用于代码块
     */
    public void m1(){
        synchronized (object){
            System.out.println("-----m1");
        }
    }
    /**
     * 作用于实例方法
     */
    public synchronized void m2(){
        System.out.println("-----m2");
    }
    /**
     * 作用于静态方法
     */
    public static synchronized void m3(){
        System.out.println("-----m3");
    }

    public static void main(String[] args) {
        // 执行方法生成target文件
    }
}

3、字节码角度分析Synchronized实现

3.1 class 编译技巧

  • 文件反编译 javap -c ***.class文件反编译,-c表示对代码进行反汇编
  • 假如需要更多信息  javap -v ***.class ,-v即-verbose输出附加信息(包括行号、本地变量表、反汇编等详细信息)

3.2 分析过程(分析上面LockSyncDemo类)

找到对应类的target文件

// 使用以下命令反编译LockSyncDemo这个类
javap -v LockSyncDemo.class

 3.2.1 分析Synchronized同步代码块

Object object = new Object();
/**
 * 作用于代码块
 */
public void m1(){
    synchronized (object){
        System.out.println("-----m1");
    }
}
public void m1();
    Code:
       0: aload_0
       1: getfield      #3                  // Field object:Ljava/lang/Object;
       4: dup
       5: astore_1
       6: monitorenter        //**注****------进入锁
       7: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      10: ldc           #5                  // String -----m1
      12: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      15: aload_1
      16: monitorexit         // **注**------退出锁
      17: goto          25
      20: astore_2
      21: aload_1
      22: monitorexit         //**注**-----这里又有一个exit???????以防出现异常,保证能够释放锁(异常释放锁)
      23: aload_2
      24: athrow
      25: return

synchronized同步代码块,实现使用的是 moniterentermoniterexit 指令(moniterexit有两个,一个异常要执行的,一个是正常执行的;但是只执行一次)

3.2.2 Synchronized普通同步方法 

/**
 * 作用于实例方法
 */
public synchronized void m2(){
    System.out.println("-----m2");
}
  public synchronized void m2();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED  // 请注意该标志ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #7                  // String -----m2
         5: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 22: 0
        line 23: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lcom/guigu/juc/synchronized_test/LockSyncDemo;

        调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置。如果设置了,执行线程会将先持有monitore然后再执行方法,最后在方法完成(无论是正常完成还是非正常完成)时释放monitor

3.2.3 Synchronized静态同步方法

/**
 * 作用于静态方法
 */
public static synchronized void m3(){
    System.out.println("-----m3");
}
public static synchronized void m3();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED //访问标志 区分该方法是否是静态同步方法
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #8                  // String -----m3
         5: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 28: 0
        line 29: 8

ACC_STATIC, ACC_SYNCHRONIZED访问标识区分该方法是否是静态同步方法。

4、从底层(OpenJDK)看Synchronized锁是什么? 

4.1 管程概念

  • 管程:Monitor(监视器),也就是我们平时说的锁。监视器锁。
  • 执行线程就要球先成功持有管程,然后才能执行方法,最后当方法完成(无论是正常完成还是非正常完成)时释放管程。在方法执行期间,执行线程持有了管程,其他任何线程都无法在获取到同一个管程。

4.2 【面试题】大厂:为什么任何一个对象都可以成为一个锁?

  • Java Object 类是所有类的父类,也就是说 Java 的所有类都继承了 Object,子类可以使用 Object 的所有方法。
  • ObjectMonitor.javaObjectMonitor.cppobjectMonitor.hpp 从openJdk中查看
  • 追溯底层可以发现每个对象天生都带着一个对象监视器
  // 追溯底层可以发现每个对象天生都带着一个对象监视器
  ObjectMonitor() {
    _header       = NULL;
    _count        = 0; //用来记录该线程获取锁的次数
    _waiters      = 0,
    _recursions   = 0;//锁的重入次数
    _object       = NULL;
    _owner        = NULL; //------最重要的----指向持有ObjectMonitor对象的线程,记录哪个线程持有了我
    _WaitSet      = NULL; //存放处于wait状态的线程队列
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ;//存放处于等待锁block状态的线程队列
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }

 

5、公平锁和非公平锁

5.1 ReentrantLock抢票案例

public class Ticket {
    private int number = 100;
    /** 默认非公平锁 */
//    ReentrantLock lock = new ReentrantLock();
    /** 公平锁 */
     ReentrantLock lock = new ReentrantLock(true);
    public void sale(){
        lock.lock();
        try{
            if(number > 0){
                System.out.println(Thread.currentThread().getName()+"卖出第:\t"+(number--)+"\t 还剩下:"+number);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(() -> { for (int i = 0; i <35; i++)  {ticket.sale();} },"a").start();
        new Thread(() -> { for (int i = 0; i <35; i++)  {ticket.sale();} },"b").start();
        new Thread(() -> { for (int i = 0; i <35; i++)  {ticket.sale();} },"c").start();
    }
}

5.2 非公平锁

ReentrantLock lock = new ReentrantLock();

非公平锁可以插队,买卖票不均匀。

指多个线程获取锁的顺序不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发环境下,有可能造成优先级反转或饥饿的状态(某个线程一直得不到锁)

 5.3 公平锁

ReentrantLock lock = new ReentrantLock(true);

一开始由一个线程先占优,后面三个线程会均匀分布

是指多个线程按照申请锁的顺序来获取锁,这里类似排队买票,先来的人先买后来的人排在队尾等的,这是公平的。

5.4 为什么又公平锁/非公平锁的设计?为什么默认是非公平锁? 

  1. 恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从CPU的角度来看,这个时间差存在的还是很明显的。所以非公平锁能更充分的利用CPU 的时间片,尽量减少 CPU 空闲状态时间(减少上下文的切换)。
  2. 使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当1个线程请求锁获取同步状态,然后释放同步状态,因为不需要考虑是否还有前驱节点,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大,所以就减少了线程的开销。

6、可重入锁(递归锁)

6.1 说明

        是指在同一个线程外层方法获取锁的时候,再进入该线程的内层方法自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。

        如果是1个有 synchronized 修饰的递归调用方法,程序第2次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚。

        所以Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。

可:可以

重:再次

入:进入

锁:同步锁

进入什么:进入同步域(即同步代码块方法/方法或显示锁锁定的代码)

一句话:一个线程中多个流程可以多次获取同一把锁,持有这把锁可以再次进入。自己可以获取自己的内部锁

6.2  可重入锁案例

6.2.1 隐式锁Synchronized

        Sunchronized是Java中的关键字,默认是可重入锁,即隐式锁

// 作用于代码块
public static void main(String[] args){
    final Object objectLockA = new Object();
    new Thread(() -> {
        synchronized (objectLockA){
            System.out.println("-----外层调用");
            synchronized (objectLockA){
                System.out.println("-----中层调用");
                synchronized (objectLockA){
                    System.out.println("-----内层调用");
                }
            }
        }
    },"a").start();
}
//-----外层调用
//-----中层调用
//-----内层调用
// 作用于方法
public class ReEntryLockDemo {
    public static void main(String[] args){
        ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo();
        reEntryLockDemo.m1();
    }

    public synchronized void m1(){
        //指的是可重复可递归调用的锁,在外层使用之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁
        System.out.println(Thread.currentThread().getName()+"\t"+"-----come in m1");
        m2();
        System.out.println(Thread.currentThread().getName()+"\t-----end m1");
    }
    public synchronized void m2(){
        System.out.println("-----m2");
        m3();
    }
    public synchronized void m3(){
        System.out.println("-----m3");
    }
}
/**
 * main  -----come in m1
 * -----m2
 * -----m3
 * main  -----end m1
 */

6.2.2 Synchronized可重入实现原理

        回看 ObjectMoitor.hpp

140行
  ObjectMonitor() {
    _header       = NULL;
    _count        = 0; //用来记录该线程获取锁的次数
    _waiters      = 0,
    _recursions   = 0;//锁的重入次数
    _object       = NULL;
    _owner        = NULL; //------最重要的----指向持有ObjectMonitor对象的线程,记录哪个线程持有了我
    _WaitSet      = NULL; //存放处于wait状态的线程队列
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ;//存放处于等待锁block状态的线程队列
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }
  • ObjectMoitor.hpp底层:每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。_count  _owner
  • 首次加锁:当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加1。
  • 重入:在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么 Java 虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁。
  • 释放锁:当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。

6.2.3 显式锁Lock 

显式锁就是指是否手动调用加锁解锁

显式锁要注意的就是 lock   unlock要成对出现

static Lock lock = new ReentrantLock();
public static void main(String[] args){
    new Thread(() -> {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t----come in 外层调用");
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "\t------come in 内层调用");
            } finally {
                lock.unlock();
            }
        } finally {
            lock.unlock();
        }
    }, "t1").start();
}

//t1  ----come in 外层调用
//t1  ------come in 内层调用

 如果没有成对出现,在多线程下会出现问题

static Lock lock = new ReentrantLock();
public static void main(String[] args){
    new Thread(() -> {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t----come in 外层调用");
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "\t------come in 内层调用");
            } finally {
                lock.unlock();
            }
        } finally {
            //lock.unlock();//-------------------------不成对|多线程情况
        }
    }, "t1").start();

    new Thread(() -> {
        lock.lock();
        try
        {
            System.out.println("t2 ----外层调用lock");
        }finally {
            lock.unlock();
        }
    },"t2").start();
}

//t1  ----come in 外层调用
//t1  ------come in 内层调用
//(t2 ----外层调用lock 假如不成对,这句话就不显示了)

7、死锁及排查

7.1 死锁是什么?

        是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。

        a跟b两个资源互相请求对方的资源

 7.2 死锁代码

public class DeadLockDemo {

    public static void main(String[] args) {
        Object lockA = new Object();
        Object lockB = new Object();
        new Thread(() -> {
            synchronized (lockA){
                System.out.println(Thread.currentThread().getName()+"\t 持有a锁,想获得b锁");
                try {
                    // 使得线程b也启动
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lockB){
                    System.out.println(Thread.currentThread().getName()+"\t 成功获得b锁");
                }
             }
        },"a").start();

        new Thread(() -> {
            synchronized (lockB){
                System.out.println(Thread.currentThread().getName()+"\t 持有b锁,想获得a锁");
                synchronized (lockA){
                    System.out.println(Thread.currentThread().getName()+"\t 成功获得a锁");
                }
            }
        },"b").start();
    }
}

7.3 何排查死锁? 

7.3.1 命令排查

jps -l 查看当前进程运行状况

jstack 进程编号 查看该进程信息

 

7.3.2 jdk自带图形化

win + r 输入 jconsole ,打开图形化工具,打开 线程 ,点击 检测死锁

 

 8、写锁(独占锁)、读锁(共享锁)、自旋锁等其他锁后续会再继续

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

郭吱吱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值