java锁

news2024/12/24 10:19:19

java锁

乐观锁和悲观锁

悲观锁

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

  • 悲观锁的实现方式

    • synchronized关键字
    • Lock的实现类都是悲观锁
  • 适合写操作多的场景,先加锁可以保证写操作时数据正确。显示的锁定之后再操作同步资源。

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

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

乐观锁

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

  • 乐观锁的实现方式

    1.版本号机制Version。(只要有人提交了就会修改版本号,可以解决ABA问题)

    • ABA问题:再CAS中想读取一个值A,想把值A变为C,不能保证读取时的A就是赋值时的A,中间可能有个线程将A变为B再变为A。
      • 解决方法:Juc包提供了一个AtomicStampedReference,原子更新带有版本号的引用类型,通过控制版本值的变化来解决ABA问题。

    2.最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。

  • 适合读操作多的场景,不加锁的性能特点能够使其操作的性能大幅提升。

//=============乐观锁的调用方式
// 保证多个线程使用的是同一个AtomicInteger
private AtomicInteger atomicInteger = new AtomicInteger();
atomicInteger.incrementAndGet();

从8种情况演示锁的案例,看看我们到底锁的是什么

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

8锁演示

  • 8锁案例

1.标准访问有ab两个线程,请问先打印邮件还是短信?邮件

class Phone {

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

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

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

        //暂停毫秒,保证a线程先启动
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            phone.sendSMS();
        }, "b").start();
    }
}
//输出结果
//-------------sendEmail
//-------------sendSMS

2.访问ab两个线程,a里面故意停3秒?邮件

class Phone {
    public synchronized void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("-------------sendEmail");

    }

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

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

        //暂停毫秒,保证a线程先启动
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            phone.sendSMS();
        }, "b").start();
    }
}
//输出结果
//-------------sendEmail
//-------------sendSMS

题目1和2说明

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

3.添加一个普通的hello方法,请问先打印邮件还是hello?hello

class Phone {
    public synchronized void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("-------------sendEmail");

    }

    public synchronized void sendSMS() {
        System.out.println("-------------sendSMS");
    }
    public void hello()
    {
        System.out.println("-------hello");
    }
}

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

        //暂停毫秒,保证a线程先启动
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            phone.hello();
        }, "b").start();
    }
}
//输出结果
//-------hello
//-------------sendEmail

题目3说明

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

4.有两部手机,请问先打印邮件(这里有个3秒延迟)还是短信?短信

class Phone {
    public synchronized void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("-------------sendEmail");

    }

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

public class Lock8Demo {
    public static void main(String[] args) {
        Phone phone = new Phone();
        Phone phone2 = new Phone();
        new Thread(() -> {
            phone.sendEmail();
        }, "a").start();

        //暂停毫秒,保证a线程先启动
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            phone2.sendSMS();
        }, "b").start();
    }
}
//输出结果
//-------------sendSMS
//-------------sendEmail

题目4说明

邮件和短信方法虽然都添加了synchronized修饰,但两个方法都是普通方法。因此锁的是对象,当调用邮件和短信使用不同对象去调用时,上锁的对象是不同的,因此这里sendSMS会优先执行再去执行sendEmail。

5.有两个静态同步方法(synchroized前加static,3秒延迟也在),有1部手机,先打印邮件还是短信?邮件

class Phone {
    public static synchronized void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("-------------sendEmail");

    }

    public static synchronized void sendSMS() {
        System.out.println("-------------sendSMS");
    }
}

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

        //暂停毫秒,保证a线程先启动
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            phone.sendSMS();
        }, "b").start();
    }
}
//输出结果
//-------------sendEmail
//-------------sendSMS

6.两个手机,有两个静态同步方法(synchroized前加static,3秒延迟也在),先打印邮件还是短信?邮件

class Phone {
    public static synchronized void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("-------------sendEmail");

    }

    public static synchronized void sendSMS() {
        System.out.println("-------------sendSMS");
    }
}

public class Lock8Demo {
    public static void main(String[] args) {
        Phone phone = new Phone();
        Phone phone2 = new Phone();
        new Thread(() -> {
            phone.sendEmail();
        }, "a").start();

        //暂停毫秒,保证a线程先启动
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            phone2.sendSMS();
        }, "b").start();
    }
}
//输出结果
//-------------sendEmail
//-------------sendSMS

题目5和6说明

5和6中,sendEmail和sendSMS都是静态方法且都被synchronized所修饰,因此两个方法是锁的是当前类的Class对象。这里虽然调用sendEmail和sendSMS方法是用的不同的对象,当都是同一把锁,因此会先执行sendEmail再去执行sendSMS。

补充说明

  • 对于普通同步方法,锁的是当前实例对象,通常指this,具体的一部部手机,所有的普通同步方法用的都是同一把锁→实例对象本身。

  • 对于静态同步方法,锁的是当前类的Class对象,如Phone,class唯一的一个模板。

  • 对于同步方法块,锁的是synchronized括号内的对象。synchronized(o)

7.一个静态同步方法,一个普通同步方法,有一部手机,请问先打印邮件还是短信?短信

class Phone {
    public static synchronized void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("-------------sendEmail");

    }

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

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

        //暂停毫秒,保证a线程先启动
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            phone.sendSMS();
        }, "b").start();
    }
}
//输出结果
//-------------sendSMS
//-------------sendEmail

8.两个手机,一个静态同步方法,一个普通同步方法,请问先打印邮件还是手机?短信

class Phone {
    public static synchronized void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("-------------sendEmail");

    }

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

public class Lock8Demo {
    public static void main(String[] args) {
        Phone phone = new Phone();
        Phone phone2 = new Phone();
        new Thread(() -> {
            phone.sendEmail();
        }, "a").start();

        //暂停毫秒,保证a线程先启动
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            phone2.sendSMS();
        }, "b").start();
    }
}
//输出结果
//-------------sendSMS
//-------------sendEmail

题目7和8说明

如上sendEmail方法是类锁,而sendSMS是对象锁。两个方法不是同一把锁不存在锁的争抢,因此会先执行sendSMS再去sendEmail。

8锁-3个体现

  • 8种锁的案例实际体现在3个地方-相当于总结
    • 作用域实例方法,当前实例加锁,进入同步代码块前要获得当前实例的锁。
    • 作用于代码块,对括号里配置的对象加锁。
    • 作用于静态方法,当前类加锁,进去同步代码前要获得当前类对象的锁

字节码角度分析synchronized实现

文件反编译技巧

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

synchronized同步代码块

/**
 * 锁同步代码块
 */
public class LockSyncDemo {
    Object object = new Object();

    public void m1(){
        synchronized (object){
            System.out.println("-----hello synchronized code block");
        }
    }

    public static void main(String[] args) {

    }
}
  • 从target中找到LockSyncDemo.class文件,右键,open in terminal,然后javap -c LockSyncDemo.class
public class com.llp.juc.lock.LockSyncDemo {
  java.lang.Object object;

  public com.llp.juc.lock.LockSyncDemo();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: new           #2                  // class java/lang/Object
       8: dup
       9: invokespecial #1                  // Method java/lang/Object."<init>":()V
      12: putfield      #3                  // Field object:Ljava/lang/Object;
      15: return

  public void m1();
    Code:
       0: aload_0
       1: getfield      #3                  // Field object:Ljava/lang/Object;
       4: dup
       5: astore_1
       6: monitorenter			//monitorenter进入锁
       7: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      10: ldc           #5                  // String -----hello synchronized code block
      12: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      15: aload_1
      16: monitorexit			//monitorexit进入锁(正常退出)
      17: goto          25
      20: astore_2
      21: aload_1
      22: monitorexit   		//monitorexit进入锁(异常退出)
      23: aload_2
      24: athrow
      25: return
    Exception table:
       from    to  target type
           7    17    20   any
          20    23    20   any

  public static void main(java.lang.String[]);
    Code:
       0: return
}

  • 总结

synchronized同步代码块,实现使用的是moniterenter和moniterexit指令(moniterexit可能有两个)

那一定是一个enter两个exit吗?(不一样,如果主动throw一个RuntimeException,发现一个enter,一个exit,还有两个athrow)

/**
 * 锁普通的同步方法
 */
public class LockSyncDemo {

    public synchronized void m2(){
        System.out.println("------hello synchronized m2");
    }

    public static void main(String[] args) {

    }
}
  public synchronized void m2();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED  //ACC_SYNCHRONIZED 表示该方法是普通的同步方法
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String ------hello synchronized m2
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 12: 0
        line 13: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lcom/llp/juc/lock/LockSyncDemo;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 17: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  args   [Ljava/lang/String;
}

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

synchronized静态同步方法

/**
 * 锁静态同步方法
 */
public class LockSyncDemo {

    public synchronized void m2(){
        System.out.println("------hello synchronized m2");
    }

    public static synchronized void m3(){
        System.out.println("------hello synchronized m3---static");
    }


    public static void main(String[] args) {

    }
}

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

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 25: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  args   [Ljava/lang/String;
}

  • 总结
    • ACC_STATIC,ACC_SYNCHRONIZED访问标志区分该方法是否是静态同步方法。

反编译synchronized锁的是什么

概念-管程

  • 管程概念

    • 管程:Monitor(监视器),也就是我们平时说的锁。监视器锁

    • 信号量及其操作原语“封装”在一个对象内部)管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。 管程提供了一种机制,管程可以看做一个软件模块,它是将共享的变量和对于这些共享变量的操作封装起来,形成一个具有一定接口的功能模块,进程可以调用管程来实现进程级别的并发控制。

    • 执行线程就要求先成功持有管程,然后才能执行方法,最后当方法完成(无论是正常完成还是非正常完成)时释放管理。在方法执行期间,执行线程持有了管程,其他任何线程都无法再获取到同一个管程。

大厂:为什么任何一个对象都可以成为一个锁?

  • 溯源

    • Java Object 类是所有类的父类,也就是说 Java 的所有类都继承了 Object,子类可以使用 Object 的所有方法。

    • ObjectMonitor.java→ObjectMonitor.cpp→objectMonitor.hpp

ObjectMonitor.cpp中引入了头文件(include)objectMonitor.hpp

140ObjectMonitor() {
    _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;
  }
  • 追溯底层可以发现每个对象天生都带着一个对象监视器

公平锁和非公平锁

ReentrantLock抢票案例

class Ticket {
    private int number = 30;
    //非公平锁
    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 class SaleTicketDemo {
    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();
    }
}

非公平锁

  • 默认是非公平锁
  • 非公平锁可以插队,买卖票不均匀。
  • 是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发环境下,有可能造成优先级翻转或饥饿的状态(某个线程一直得不到锁)

公平锁

  • ReentrantLock lock = new ReentrantLock(true);
  • 买卖票一开始a占优,后面a b c a b c a b c均匀分布
  • 是指多个线程按照申请锁的顺序来获取锁,这里类似排队买票,先来的人先买后来的人在队尾排着,这是公平的。

为什么会有公平锁/非公平锁的设计?为什么默认是非公平?

恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从CPU的角度来看,这个时间差存在的还是很明显的。所以非公平锁能更充分的利用CPU 的时间片,尽量减少 CPU 空闲状态时间。

使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当1个线程请求锁获取同步状态,然后释放同步状态,因为不需要考虑是否还有前驱节点,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大,所以就减少了线程的开销。

什么时候用公平?什么时候用非公平?

如果为了更高的吞吐量,很显然非公平锁是比较合适的,因为节省很多线程切换时间,吞吐量自然就上去了;
否则那就用公平锁,大家公平使用。

可重入锁(又名递归锁)

可重入锁说明

可重入锁又名递归锁

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

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

所以Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。
在这里插入图片描述

“可重入锁”详细解释

  • 可:可以
  • 重:再次
  • 入:进入
  • 锁:同步锁
  • 进入什么:进入同步域(即同步代码块/方法或显示锁锁定的代码)
  • 一句话:一个线程中的多个流程可以获取同一把锁,持有这把锁可以再次进入。自己可以获取自己的内部锁。

可重入锁种类

隐式锁Synchronized

  • synchronized是java中的关键字,默认可重入锁,即隐式锁

在同步块中

public class ReEntryLockDemo {
    public static void main(String[] args) {
        final Object object = new Object();
		//一个线程打进来外层同步代码块获取到锁,在进入内层同步代码块时可以自动获取到锁
        new Thread(() -> {
            synchronized (object) {
                System.out.println("-----外层调用");
                synchronized (object) {
                    System.out.println("-----中层调用");
                    synchronized (object) {
                        System.out.println("-----内层调用");
                    }
                }
            }
        }, "a").start();
    }
}
//-----外层调用
//-----中层调用
//-----内层调用

在同步方法中

public class ReEntryLockDemo {
    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(Thread.currentThread().getName() + "\t" + "-----come in m2");
        m3();
    }

    public synchronized void m3() {
        System.out.println(Thread.currentThread().getName() + "\t" + "-----come in m3");
    }

    public static void main(String[] args) {
        ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo();
        reEntryLockDemo.m1();
    }
}
//从下面的打印结果可以看到通一个线程获取到外层锁可以进入内存的的同步方法
//main	-----come in m1
//main	-----come in m2
//main	-----come in m3
//main	-----end m1
Synchronized的重入实现机理
  • 回顾前面的ObjectMoitor.hpp
140ObjectMonitor() {
    _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。计数器为零代表锁已被释放。

显式锁Lock

  • 显式锁(即Lock)也有ReentrantLock这样的可重入锁

synchronized是隐式锁。Lock是显示锁,所谓的显示和隐式就是在使用的时候,使用者要不要手动写代码去获取锁和释放锁的操作。

  • 注意:lock unlock成对
public class ReEntryLockDemo {
    static Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        new Thread(() -> {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "\t----come in 外层调用");
            try {
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + "\t------come in 内层调用");
                } finally {
                    //和内层lock对应
                    lock.unlock();
                }
            } finally {
                //和外层lock对应
                lock.unlock();
            }
        }, "t1").start();
    }
}
//t1  ----come in 外层调用
//t1  ------come in 内层调用
  • 假如lock unlock不成对,单线程情况下问题不大,但多线程下出问题
public class ReEntryLockDemo {
    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得不到释放,当另一个线程进入外层lock时一直获取不到锁一直堵塞
                //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 假如不成对,这句话就不显示了)

可以看到灯一直亮着,外层锁一直得不到释放,而第二线程一直拿不到锁就在那里一直等待

image-20221201140318413

死锁及排查

死锁是什么

  • 死锁

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

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

  • 死锁产生的原因

    • 系统资源不足

    • 进程运行推进的顺序不合适

    • 资源分配不当
      在这里插入图片描述

一个死锁代码case

public class DeadLockDemo {
    public static void main(String[] args) {
        Object objectA = new Object();
        Object objectB = new Object();

        new Thread(() -> {
            synchronized (objectA) {
                System.out.println(Thread.currentThread().getName() + "\t 持有a锁,想获得b锁");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }//使得线程b也启动
                synchronized (objectB) {
                    System.out.println(Thread.currentThread().getName() + "\t 成功获得b锁");
                }
            }
        }, "A").start();

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

}
//输出结果
//A	 持有a锁,想获得b锁
//B	 持有b锁,想获得a锁

如何排查死锁

纯命令

  • jps -l 查看当前进程运行状况
  • jstack 进程编号 查看该进程信息

image-20221201142826453

图形化

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

在这里插入图片描述

image-20221201143032842

小总结

  • 指针指向monitor对象(也称为管程或监视器锁)的起始地址。每个对象都存在着一个monitor与之关联,当一个monitor被某个线程持有后,它便处于锁定状态。在Java虚拟机(HotSpot)中,monitor是由ObjectMonitor实现的,其主要数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp,C++实现的)

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/51221.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

面向物联网应用的6G技术

摘要 在物联网(Internet of Things,IoT)快速发展和5G已经规模化的商业部署的背景下,在不久的将来,5G的技术指标将无法完全满足大规模IoT的应用需求。而6G技术由于其具备高传输、低时延等出色的性能指标,受到了学术界和工业界的广泛关注。因此,为了促使IoT网络能够更好地发…

此框架是SQL Server增量订阅,用来监听增删改数据库数据变更

目前仅支持SQL Server&#xff0c;后续会支持MySQL和Oracle&#xff0c;Nuget上可以下载安装 或者使用Nuget命令添加包 dotnet add package Kogel.Subscribe.Mssql --version 0.0.0.1 可以用来处理DB主从同步&#xff0c;跨库同步&#xff0c;数据备份&#xff0c;同步ES&…

AI绘画软件汇总

AI绘画软件汇总 AI绘图在线体验 二次元绘图 在线体验地址:Stable Diffusion 模型包括&#xff1a; NovelAI&#xff0c;NovelAI的模型训练使用了数千个网站的数十亿张图片&#xff0c;包括 Pixiv、Twitter、DeviantArt、Tumblr等网站的作品。 Waifu&#xff0c;waifu的模型…

ShareSDK for Unity

本文档使用Unity2019进行演示 下载unitypackage 从Mob的github地址下载ShareSDK.unitypackage&#xff1a;Git地址&#xff0c;如下图所示 )![image.png]//download.sdk.mob.com/2022/06/22/15/165588252810937.61.png) 下载完成后得到一个.unitypackage结尾的文件&#xf…

2022年12月全国DAMA-CDGA/CDGP数据治理认证招生简章

20DAMA认证为数据管理专业人士提供职业目标晋升规划&#xff0c;彰显了职业发展里程碑及发展阶梯定义&#xff0c;帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力&#xff0c;促进开展工作实践应用及实际问题解决&#xff0c;形成企业所需的新数字经济下的核心职…

R语言stan进行基于贝叶斯推断的回归模型

可以从许多统计软件包中运行Stan。到目前为止&#xff0c;我一直在从R运行Stan。 我们围绕stan进行一些咨询&#xff0c;帮助客户解决独特的业务问题。 简单线性回归 第一步是为Stan模型编写文件。这包含一个文件linreg.stan&#xff1a; 视频&#xff1a;线性回归中的贝叶斯…

新闻舆情管理平台开发,监控舆情发展趋势

打造企业良好声誉可能需要几年、十几年甚至更久&#xff0c;而毁掉它只需要短短几分钟。尤其是互联网时代下&#xff0c;人们接收信息的速度越来越快&#xff0c;在新闻发出去的几分钟内就能迅速占据热搜榜。而且网络上每天都会产生上亿条信息&#xff0c;单纯的依靠人工进行监…

openEuler 通过 手工方式 安装 ceph 步骤 Cephadm无法应用到openEuler 提醒不支持

ceph集群在openEuler手工安装过程Cephadm安装步骤前置要求1.openEuler版本2. Python 33. Systemd4. Time synchronization (such as chrony or NTP)5. LVM2 for provisioning storage devices安装1. 创建用户ceph2. 安装 ceph3. 生成配置项3.1 机器及组件规划列表3.2 ceph.conf…

Python第三方库之nibabel

1.nibabel简介 NiBabel提供对一些常见医学和神经影像文件格式的读/写访问&#xff0c;包括ANALYZE&#xff08;plain&#xff0c;SPM99&#xff0c;SPM2及更高版本&#xff09;&#xff0c;GIFTI&#xff0c;NIfTI1&#xff0c;NIfTI2&#xff0c;CIFTI-2&#xff0c;MINC1&am…

[附源码]SSM计算机毕业设计疫情防控期间人员档案追寻系统JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

RocketMQ系列——搭建Namesrv源码调试环境整理

目录 RocketMQ系列-搭建Namesrv源码调试环境 Namesrv源码调试环境搭建 导入项目到IDEA 创建所需目录 环境配置 启动Namesrv 总结 RocketMQ系列-搭建Namesrv源码调试环境 在学习任何一个技术框架的时候&#xff0c;我们通常都是先了解是什么&#xff0c;有什么作用、解决…

Java流程控制语句

流程控制语句 在一个程序执行的过程中&#xff0c;各条语句的执行顺序对程序的结果是有直接影响的。所以&#xff0c;我们必须清楚每条语句的执行流程。而且&#xff0c;很多时候要通过控制语句的执行顺序来实现我们想要的功能。 流程控制语句分类 顺序结构、分支结构&#…

【毕业设计】深度学习社交安全距离检测系统 - python opencv

文章目录0 前言1 课题背景2 实现效果3 相关技术3.1 YOLOV43.2 基于 DeepSort 算法的行人跟踪4 最后0 前言 &#x1f525; Hi&#xff0c;大家好&#xff0c;这里是丹成学长的毕设系列文章&#xff01; &#x1f525; 对毕设有任何疑问都可以问学长哦! 这两年开始&#xff0c…

鲜花商城|基于Springboot实现鲜花商城系统

作者主页&#xff1a;编程千纸鹤 作者简介&#xff1a;Java、前端、Pythone开发多年&#xff0c;做过高程&#xff0c;项目经理&#xff0c;架构师 主要内容&#xff1a;Java项目开发、毕业设计开发、面试技术整理、最新技术分享 收藏点赞不迷路 关注作者有好处 文末获得源码 …

xgboost 为什么拟合残差能获得更好的效果(思考)

以时序预测为例&#xff1a; 现在要 预测2022年之后的值&#xff0c;可以预测下降幅度&#xff08;和预测残差的步骤一样&#xff09;。 假设有一个隐藏的规律&#xff1a;对于21年的高峰&#xff0c;22年的下降幅度会更大&#xff08;如time3是&#xff0c;下降幅度会比其他的…

Spring依赖注入源码解析(下)

文章目录前言本章目标resolveDependency—解决依赖查找1、doResolveDependency2、Autowreid寻找依赖流程图依赖注入完整流程图前言 在上一篇文章Spring依赖注入源码解析&#xff08;上&#xff09;中&#xff0c;主要介绍了寻找注入点、以及注入源码分析 本章目标 这一篇主要…

市面上最适合跑步用的耳机有哪些、分享五款最优秀的跑步耳机

随着人们日益对健康的重视&#xff0c;”全民健身“正在全国&#xff0c;乃至全世界蔓延开来&#xff0c;其中跑步锻炼凭借着门槛低&#xff0c;益处多成为了大部分人的健身的首选。而随着跑步大军的壮大&#xff0c;国内蓝牙耳机市场也是一片火热。其中蓝牙无线运动耳机凭借着…

【python小项目】用python写一个小工具——番茄钟

用python写一个小工具——番茄钟 最近听到朋友说在用番茄钟&#xff0c;有点兴趣也想下载一个来用用&#xff0c;后面仔细一想这玩意做起来也不难&#xff0c;索性自己顺手写一个算了&#xff0c;在这里也分享给大家了 一、功能简述 番茄钟即番茄工作法&#xff0c;番茄工作法…

产品经理必备的能力有哪些?

从一名普通的产品经理到一名优秀的产品经理要经历什么&#xff1f;哪些又是产品经理必备的能力&#xff1f;产品经理对能力的需求也不尽相同&#xff0c;在不同的团队合作模式下&#xff0c;还必须懂得各种能力。 一、业务分析能力 数据分析能力该是什么样的呢 1、有数据意识…

indexDB 本地数据库

indexDB 本地数据库 IndexedDB是一种使用浏览器存储大量数据的方法&#xff0c;它创造的数据可以被查询&#xff0c;并且可以离线使用。 优点&#xff1a;空间大小&#xff0c;大于250M&#xff1b;支持二进制&#xff1a;IndexedDB不但可以存储对象&#xff0c;字符串等&#…