• JUC(4):Java “锁“事一览


    一、Lock

    1.1 概述

    Lock 是 Java.util.concurrent.locks 包下的接口,Lock 实现提供了比 synchronized 关键字更广泛的锁操作,锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象

    1.2 Lock 接口

    Lock 是一个接口,接口的实现类有 ReentrantLock 和内部类 ReentrantReadWriteLock

    1. public interface Lock
    2. {
    3. void lock();
    4. void lockInterruptibly() throws InterruptedException;
    5. boolean tryLock();
    6. boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    7. void unlock();
    8. Condition newCondition();
    9. }
    10. 复制代码

    1.2.1 lock

    lock () 方法是平常使用最多的一个方法,就是用来获取锁,如果锁已被其他线程获取,则进行等待。

    采用 Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此,一般来说,使用 Lock 必须在 try-catch 块中进行,并且将释放锁的操作放在 finally 块中进行,以保证锁一定被释放,防止死锁的发生。通常使用 Lock 来进行同步的话,是以下面这种形式去使用的。

    1. Lock lock = ...;
    2. lock.lock();
    3. try{
    4. //处理任务
    5. }
    6. catch(Exception ex){
    7. }finally{
    8. lock.unlock(); //释放锁
    9. }
    10. 复制代码

    1.2.2 tryLock

    该方法表示用来尝试获取锁,但是该方法是有返回值的,如果获取成功,则返回 true,如果获取失败,则返回 false,也就是说这个方法无论如何都会立即返回,在拿不到锁时也不会一直在那等待。

    1. private static Lock lock = new ReentrantLock();
    2. public static void main(String[] args) {
    3. if(lock.tryLock()) {
    4. try{
    5. System.out.println("成功获取锁!!");
    6. }catch(Exception e){
    7. e.printStackTrace();
    8. }finally{
    9. lock.unlock();
    10. }
    11. }else {
    12. System.out.println("未获取锁,先干别的");
    13. }
    14. }
    15. 复制代码

    1.2.2 newCondition

    关键字 synchronized 与 wait\notify 这两个方法可以一起使用实现等待通知功能,lock 锁的 newCondition 方法返回 Condition 对象,Condition 类也可以实现等待\通知模式。

    用 notify()通知时,JVM 会随机唤醒某个等待的线程,使用 Condition 类可以进行选择性的通知。它有两个常用方法:

    • await()会使当前线程等待,同时会释放锁,当其他线程调用 signal 时,线程会重新获得锁并继续执行。
    • signal()用于唤醒一个等待的线程。

    1.2.3 ReentrantLock(可重入锁)

    ReentrantLock 是唯一实现了 Lock 接口的类,并且 ReentrantLock 提供了更多的方法。下面通过一些实例看具体如何使用。

    1. public class LockTest {
    2. private ArrayList<Integer> arrayList = new ArrayList<Integer>();
    3. public static void main(String[] args) {
    4. LockTest test = new LockTest();
    5. new Thread(()->{test.insert(Thread.currentThread());}).start();
    6. new Thread(()->{test.insert(Thread.currentThread());}).start();
    7. new Thread(()->{test.insert(Thread.currentThread());}).start();
    8. }
    9. public void insert(Thread thread) {
    10. Lock lock = new ReentrantLock(); //注意这个地方
    11. lock.lock();
    12. try {
    13. System.out.println(thread.getName()+"得到了锁");
    14. Thread.sleep(1000);
    15. } catch (Exception e) {
    16. // TODO: handle exception
    17. }finally {
    18. lock.unlock();
    19. System.out.println(thread.getName()+"释放了锁");
    20. }
    21. }
    22. }
    23. 复制代码

    ReentrantLock 意为可重入锁,关于可重入锁的概念将在后面详细讲述。它与 synchronized 一样,一个线程可以多次获取同一个锁。然而它比 synchronized 更安全,不会在 trylock 失败的时候发生死锁。

    1.2.4 ReadWriteLock

    ReadWriteLock 它是一个接口,在它里面只定义了两个方法。

    1. public interface ReadWriteLock {
    2. /**
    3. * Returns the lock used for reading.
    4. */
    5. Lock readLock();
    6. /**
    7. * Returns the lock used for writing.
    8. */
    9. Lock writeLock();
    10. }
    11. 复制代码

    两个方法,一个用来获取写锁,另一个用来获取读锁。

    也就是说将文件的读写操作分开,分成 2个锁来分配给线程,从而使得多个线程可以同时读操作,或者一个线程存在写操作,读写不能同时存在。

    下面的 ReentrantReadWriteLock 实现了ReadWriteLock 接口。

    该实现类里面提供了两个主要的方法:readLock()和 writeLock() 用来获取读锁和写锁;

    1. public class ReadWriteLockTest {
    2. private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    3. public static void main(String[] args) {
    4. final ReadWriteLockTest test = new ReadWriteLockTest();
    5. new Thread(()->{test.get(Thread.currentThread());}).start();
    6. new Thread(()->{test.get(Thread.currentThread());}).start();
    7. new Thread(()->{test.set(Thread.currentThread());}).start();
    8. }
    9. public void get(Thread thread)
    10. {
    11. rwl.readLock().lock();
    12. try {
    13. System.out.println(thread.getName()+"正在进行读操作");
    14. Thread.sleep(1000);
    15. System.out.println(thread.getName()+"读操作完毕");
    16. } catch (InterruptedException e) {
    17. throw new RuntimeException(e);
    18. } finally
    19. {
    20. rwl.readLock().unlock();
    21. }
    22. }
    23. public void set(Thread thread)
    24. {
    25. rwl.writeLock().lock();
    26. try {
    27. System.out.println(thread.getName()+"正在进行写操作");
    28. Thread.sleep(2000);
    29. System.out.println(thread.getName()+"写操作完毕");
    30. } catch (InterruptedException e) {
    31. throw new RuntimeException(e);
    32. } finally
    33. {
    34. rwl.writeLock().unlock();
    35. }
    36. }
    37. }
    38. 复制代码

    线程1 和线程2 可以同时读取数据。而线程3写锁则互斥。

    • 如果一个线程已经占用了读锁,其他线程要申请写锁,就要等待释放读锁;
    • 如果一个线程已经占用了写锁,其他线程如果要申请读锁,就要等待释放写锁;

    二、synchronized 与 Lock 的区别

    1、首先 synchronized 是 Java 内置关键字,在 JVM 层面,Lock 是个 Java 类;

    2、synchronized 无法判断是否获取锁的状态,Lock 可以判断是否获取到锁;

    3、synchronized 会自动释放锁(a 线程执行完同步代码会释放锁;b 线程执行过程中发生异常会释放锁)

    • Lock 需要在 finally 中手工释放锁(unLock方法释放锁),否则容易造成线程死锁。

    4、用 synchronized 关键字的两个线程1 和线程2 ,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了。

    5、synchronized 是锁可重入、不可中断,非公平,而 Lock 锁可重入,可判断,可公平(两者皆可)

    6、Lock 锁适合大量同步的代码的同步问题,synchronized 锁适合代码少量的同步问题。

    三、synchronized

    synchronized 的作用是保证在同一时刻,被修饰的代码块或方法只会有一个线程执行,以达到保证并发安全的效果。

    synchronized 是 Java 中解决并发问题的一个常用的方法。

    主要作用有三个:

    • 原子性:确保先互斥访问同步代码
    • 可见性:保证共享变量的修改能够及时可见
    • 有序性:解决重排序问题。

    3.1 三种应用

    1、修饰实例方法,作用于当前实例,进入同步代码前需要先获取实例的锁

    2、修饰静态方法,作用于类的 Class 对象,进入修饰的静态方法前需要先获取类的 Class 对象的锁

    3、修饰代码块,需要指定加锁对象(记作 lockobj),在进入同步代码块前需要先获取 lockobj 的锁

    3.1.1 synchronized 作用于实例对象

    synchronize作用于实例方法需要注意:

    1. 实例方法上加synchronized,线程安全的前提是,多个线程操作的是同一个实例,如果多个线程作用于不同的实例,那么线程安全是无法保证的
    2. 同一个实例的多个实例方法上有synchronized,这些方法都是互斥的,同一时间只允许一个线程操作同一个实例的其中的一个synchronized方法

    3.1.2 synchronized 作用于静态方法

    当synchronized作用于静态方法时,锁的对象就是当前类的Class对象。

    3.1.3 synchronized 同步代码块

    3.2字节码分析

    • synchronized 同步代码块,实际使用的是 monitorenter 和 monitorexit 指令;
      • 一般情况下就是1 个 enter 对应 2个 exit
    • synchronized 普通同步方法
      • javap -v class 文件反编译
      • 调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程会将先持有 monitor 锁,然后再执行方法,最后在方法完成时释放 monitor
    • synchronized 静态同步方法
      • 比普通方法多了个 ACC_STATIC 锁,来判断是否静态同步方法

    3.3 synchronized 底层原语分析

    天生带着 Object 的属性。

    四、悲观锁与悲观锁

    4.1 悲观锁

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

    synchronized 关键字和 Lock 的实现类都是悲观锁。

    • 适合写操作多的场景,先加锁可以保证写操作时数据正确。
    • 显式的锁定之后再操作同步资源。
    1. //=============悲观锁的调用方式
    2. public synchronized void m1()
    3. {
    4. //加锁后的业务逻辑......
    5. }
    6. // 保证多个线程使用的是同一个lock对象的前提下
    7. ReentrantLock lock = new ReentrantLock();
    8. public void m2() {
    9. lock.lock();
    10. try {
    11. // 操作同步资源
    12. }finally {
    13. lock.unlock();
    14. }
    15. }
    16. 复制代码

    4.2 乐观锁

    乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有么有别的线程更新了这个数据。

    如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作

    乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。

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

    乐观锁则直接去操作同步资源,是一种无锁算法,得之我幸不得我命,再抢

    乐观锁一般有两种实现方式:

    1. 采用版本号机制
    2. CAS(Compare-and-Swap,即比较并替换)算法实现
    1. //=============乐观锁的调用方式
    2. // 保证多个线程使用的是同一个AtomicInteger
    3. private AtomicInteger atomicInteger = new AtomicInteger();
    4. atomicInteger.incrementAndGet();
    5. 复制代码

    五、八锁案例

    原则:能用无锁就不要用锁。能用对象锁,就不用要类锁。

    5.1 案例说明

    线程操纵资源类

    5.1.1 情况1

    1. class Phone{
    2. public synchronized void sendEmail(){
    3. System.out.println("发送邮件!");
    4. }
    5. public synchronized void sendMsg(){
    6. System.out.println("发送短信!");
    7. }
    8. }
    9. public class Lock8 {
    10. public static void main(String[] args) {
    11. Phone phone = new Phone();
    12. new Thread(()->{
    13. phone.sendEmail();
    14. },"a").start();
    15. new Thread(()->{
    16. phone.sendMsg();
    17. },"b").start();
    18. }
    19. }
    20. //发送邮件!
    21. //发送短信!
    22. 复制代码

    5.1.2 情况2

    在发送邮件方法中假如暂停 3秒钟,先打印哪个?

    • 答:是用的同一个对象,锁的是同一个对象。
    1. class Phone{
    2. public synchronized void sendEmail(){
    3. try {
    4. TimeUnit.SECONDS.sleep(3);
    5. } catch (InterruptedException e) {
    6. e.printStackTrace();
    7. }
    8. System.out.println("发送邮件!");
    9. }
    10. public synchronized void sendMsg(){
    11. System.out.println("发送短信!");
    12. }
    13. }
    14. public class Lock8 {
    15. public static void main(String[] args) {
    16. Phone phone = new Phone();
    17. new Thread(()->{
    18. phone.sendEmail();
    19. },"a").start();
    20. new Thread(()->{
    21. phone.sendMsg();
    22. },"b").start();
    23. }
    24. }
    25. ======
    26. 发送邮件!
    27. 发送短信!
    28. 复制代码

    5.1.3 情况3

    加入一个普通的方法。

    答:hello 不用去拿锁

    1. class Phone{
    2. public synchronized void sendEmail(){
    3. try {
    4. TimeUnit.SECONDS.sleep(3);
    5. } catch (InterruptedException e) {
    6. e.printStackTrace();
    7. }
    8. System.out.println("发送邮件!");
    9. }
    10. public synchronized void sendMsg(){
    11. System.out.println("发送短信!");
    12. }
    13. public void hello(){
    14. System.out.println("hello!");
    15. }
    16. }
    17. public class Lock8 {
    18. public static void main(String[] args) {
    19. Phone phone = new Phone();
    20. new Thread(()->{
    21. phone.sendEmail();
    22. },"a").start();
    23. new Thread(()->{
    24. phone.hello();
    25. },"b").start();
    26. }
    27. }
    28. ======
    29. hello!
    30. 发送邮件!
    31. 复制代码

    5.1.4 情况4

    两个对象

    答:此时锁的不是同个对象

    1. class Phone{
    2. public synchronized void sendEmail(){
    3. try {
    4. TimeUnit.SECONDS.sleep(3);
    5. } catch (InterruptedException e) {
    6. e.printStackTrace();
    7. }
    8. System.out.println("发送邮件!");
    9. }
    10. public synchronized void sendMsg(){
    11. System.out.println("发送短信!");
    12. }
    13. public void hello(){
    14. System.out.println("hello!");
    15. }
    16. }
    17. public class Lock8 {
    18. public static void main(String[] args) {
    19. Phone phone = new Phone();
    20. Phone phone2 = new Phone();
    21. new Thread(()->{
    22. phone.sendEmail();
    23. },"a").start();
    24. new Thread(()->{
    25. phone2.sendMsg();
    26. },"b").start();
    27. }
    28. }
    29. ====
    30. 发送短信!
    31. 发送邮件!
    32. 复制代码

    5.1.5 情况5

    加上静态方法

    答:静态方法锁的是类

    1. class Phone{
    2. public static synchronized void sendEmail(){
    3. try {
    4. TimeUnit.SECONDS.sleep(3);
    5. } catch (InterruptedException e) {
    6. e.printStackTrace();
    7. }
    8. System.out.println("发送邮件!");
    9. }
    10. public static synchronized void sendMsg(){
    11. System.out.println("发送短信!");
    12. }
    13. public void hello(){
    14. System.out.println("hello!");
    15. }
    16. }
    17. public class Lock8 {
    18. public static void main(String[] args) {
    19. Phone phone = new Phone();
    20. Phone phone2 = new Phone();
    21. new Thread(()->{
    22. phone.sendEmail();
    23. },"a").start();
    24. new Thread(()->{
    25. phone.sendMsg();
    26. },"b").start();
    27. }
    28. }
    29. ==
    30. 发送邮件!
    31. 发送短信!
    32. 复制代码

    5.1.6 情况6

    1. class Phone{
    2. public static synchronized void sendEmail(){
    3. try {
    4. TimeUnit.SECONDS.sleep(3);
    5. } catch (InterruptedException e) {
    6. e.printStackTrace();
    7. }
    8. System.out.println("发送邮件!");
    9. }
    10. public static synchronized void sendMsg(){
    11. System.out.println("发送短信!");
    12. }
    13. public void hello(){
    14. System.out.println("hello!");
    15. }
    16. }
    17. public class Lock8 {
    18. public static void main(String[] args) {
    19. Phone phone = new Phone();
    20. Phone phone2 = new Phone();
    21. new Thread(()->{
    22. phone.sendEmail();
    23. },"a").start();
    24. new Thread(()->{
    25. phone2.sendMsg();
    26. },"b").start();
    27. }
    28. }
    29. ====
    30. 发送邮件!
    31. 发送短信!
    32. 复制代码

    5.1.7 情况7

    1. class Phone{
    2. public static synchronized void sendEmail(){
    3. try {
    4. TimeUnit.SECONDS.sleep(3);
    5. } catch (InterruptedException e) {
    6. e.printStackTrace();
    7. }
    8. System.out.println("发送邮件!");
    9. }
    10. public synchronized void sendMsg(){
    11. System.out.println("发送短信!");
    12. }
    13. public void hello(){
    14. System.out.println("hello!");
    15. }
    16. }
    17. public class Lock8 {
    18. public static void main(String[] args) {
    19. Phone phone = new Phone();
    20. Phone phone2 = new Phone();
    21. new Thread(()->{
    22. phone.sendEmail();
    23. },"a").start();
    24. new Thread(()->{
    25. phone2.sendMsg();
    26. },"b").start();
    27. }
    28. }
    29. =====
    30. 发送短信!
    31. 发送邮件!
    32. 复制代码

    5.1.8 情况8

    1. class Phone{
    2. public static synchronized void sendEmail(){
    3. try {
    4. TimeUnit.SECONDS.sleep(3);
    5. } catch (InterruptedException e) {
    6. e.printStackTrace();
    7. }
    8. System.out.println("发送邮件!");
    9. }
    10. public synchronized void sendMsg(){
    11. System.out.println("发送短信!");
    12. }
    13. public void hello(){
    14. System.out.println("hello!");
    15. }
    16. }
    17. public class Lock8 {
    18. public static void main(String[] args) {
    19. Phone phone = new Phone();
    20. Phone phone2 = new Phone();
    21. new Thread(()->{
    22. phone.sendEmail();
    23. },"a").start();
    24. new Thread(()->{
    25. phone.sendMsg();
    26. },"b").start();
    27. }
    28. }
    29. =====
    30. 发送短信!
    31. 发送邮件!
    32. 复制代码

    5.2 总结

    由 情况1-2 得知:

    一个对象里面如果有多个 synchronized 方法,某一时刻内,只要一个线程去调用其中的一个 synchronized 方法了。

    其他的线程都只能等待,换句话说。某一个时刻内,只能有唯一的一个线程去访问这些 synchronized 方法。

    锁的是当前对象的 this,被锁定后,其他的线程都不能进入当前对象的其他的 synchronized 方法。

    加上静态方法,此时锁的是类

    六、公平锁和非公平锁

    公平锁:

    • 是指多个线程按照申请锁的顺序来获取锁,这里类似排队买票,先来的人先买后来的人在队尾排着,这是公平的
    • Lock lock = new ReetrantLock(true) true 表示公平锁,先来先得。

    非公平锁:

    • 是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发环境下,有可能造成优先级翻转或者饥饿的状态(某一个线程一直得不到锁)
    • Lock lock = new ReetrantLock(false) 默认就是非公平锁,可以不写

    七、可重入锁(递归锁)

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

    如果是1个有 synchronzied 修饰的递归方法,程序第二次进入被自己阻塞了岂不是天大的笑话。所以Java 中 ReetrantLock 和 synchronized 都是可重入锁。

    可重入锁的一个优点是可一定程度避免死锁

    7.1可重入锁这四个字分开来解析

    可:可以。

    重:再次

    入:进入

    锁:同步锁

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

    7.2 可重入锁种类

    1、隐式锁(synchronized 关键字使用的锁)默认是可重入锁

    指的是可重复可递归调用的锁。

    在外层使用锁之后,在内层仍然可以使用,并且不发生死锁。

    简单的来说就是:在一个synchronized修饰的方法或代码块的内部调用本类的其他synchronized修饰的方法或代码块时,是永远可以得到锁的

    与可重入锁相反,不可重入锁不可递归调用,递归调用就发生死锁。

    同步块:

    1. public class ReEntryLockDemo{
    2. public static void main(String[] args){
    3. final Object objectLockA = new Object();
    4. new Thread(() -> {
    5. synchronized (objectLockA){
    6. System.out.println("-----外层调用");
    7. synchronized (objectLockA){
    8. System.out.println("-----中层调用");
    9. synchronized (objectLockA){
    10. System.out.println("-----内层调用");
    11. }
    12. }
    13. }
    14. },"a").start();
    15. }
    16. }
    17. 复制代码

    同步方法:

    1. public class ReEntryLockDemo{
    2. public synchronized void m1(){
    3. System.out.println("-----m1");
    4. m2();
    5. }
    6. public synchronized void m2(){
    7. System.out.println("-----m2");
    8. m3();
    9. }
    10. public synchronized void m3(){
    11. System.out.println("-----m3");
    12. }
    13. public static void main(String[] args){
    14. ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo();
    15. reEntryLockDemo.m1();
    16. }
    17. }
    18. 复制代码

    2、显示锁(Lock)也有 ReetrantLock 这样的可重入锁

    1. public class Demo4 {
    2. private static int num = 0;
    3. private static ReentrantLock lock = new ReentrantLock();
    4. private static void add() {
    5. lock.lock();
    6. lock.lock();
    7. try {
    8. num++;
    9. } finally {
    10. lock.unlock();
    11. lock.unlock();
    12. }
    13. }
    14. public static class T extends Thread {
    15. @Override
    16. public void run() {
    17. for (int i = 0; i < 10000; i++) {
    18. Demo4.add();
    19. }
    20. }
    21. }
    22. public static void main(String[] args) throws InterruptedException {
    23. T t1 = new T();
    24. T t2 = new T();
    25. T t3 = new T();
    26. t1.start();
    27. t2.start();
    28. t3.start();
    29. t1.join();
    30. t2.join();
    31. t3.join();
    32. System.out.println(Demo4.num);
    33. }
    34. }
    35. 复制代码

    上面代码中add()方法中,当一个线程进入的时候,会执行2次获取锁的操作,运行程序可以正常结束,并输出和期望值一样的30000,假如ReentrantLock是不可重入的锁,那么同一个线程第2次获取锁的时候由于前面的锁还未释放而导致死锁,程序是无法正常结束的。ReentrantLock命名也挺好的Re entrant Lock,和其名字一样,可重入锁。

    代码中还有几点需要注意:

    1. lock()方法和unlock()方法需要成对出现,锁了几次,也要释放几次,否则后面的线程无法获取锁了;可以将add中的unlock删除一个事实,上面代码运行将无法结束
    2. unlock()方法放在finally中执行,保证不管程序是否有异常,锁必定会释放
    1. /**
    2. * @create 2020-05-14 11:59
    3. * 在一个Synchronized修饰的方法或代码块的内部调用本类的其他Synchronized修饰的方法或代码块时,是永远可以得到锁的
    4. */
    5. public class ReEntryLockDemo{
    6. static Lock lock = new ReentrantLock();
    7. public static void main(String[] args){
    8. new Thread(() -> {
    9. lock.lock();
    10. try
    11. {
    12. System.out.println("----外层调用lock");
    13. lock.lock();
    14. try
    15. {
    16. System.out.println("----内层调用lock");
    17. }finally {
    18. // 这里故意注释,实现加锁次数和释放次数不一样
    19. // 由于加锁次数和释放次数不一样,第二个线程始终无法获取到锁,导致一直在等待。
    20. lock.unlock(); // 正常情况,加锁几次就要解锁几次
    21. }
    22. }finally {
    23. lock.unlock();
    24. }
    25. },"a").start();
    26. new Thread(() -> {
    27. lock.lock();
    28. try
    29. {
    30. System.out.println("b thread----外层调用lock");
    31. }finally {
    32. lock.unlock();
    33. }
    34. },"b").start();
    35. }
    36. }
    37. 复制代码

    7.3 synchronized 可重入锁的实现机理

    每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。

    当执行 monitorenter 时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java 虚拟机会将该所对象的持有线程设置为当前线程,并且将其计数器加1 .

    在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么 Java 虚拟机可以将其计数器加1 ,否则需要等待,直至持有线程释放该锁。

    当执行 monitorexit 时,Java 将锁对象的计数器 减少1,计数器为零代表锁被释放。

    八、死锁

    死锁是指两个或两个以上线程在执行过程中,因为争抢资源而造成的一种互相等待的现象。

    8.1 产生死锁的原因

    1、系统资源不足

    2、进程运行推进的顺序不合适

    3、资源分配不当

    1. public class DeadLockDemo{
    2. public static void main(String[] args){
    3. final Object objectLockA = new Object();
    4. final Object objectLockB = new Object();
    5. new Thread(() -> {
    6. synchronized (objectLockA){
    7. System.out.println(Thread.currentThread().getName()+"\t"+"自己持有A,希望获得B");
    8. //暂停几秒钟线程
    9. try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
    10. synchronized (objectLockB)
    11. {
    12. System.out.println(Thread.currentThread().getName()+"\t"+"A-------已经获得B");
    13. }
    14. }
    15. },"A").start();
    16. new Thread(() -> {
    17. synchronized (objectLockB){
    18. System.out.println(Thread.currentThread().getName()+"\t"+"自己持有B,希望获得A");
    19. //暂停几秒钟线程
    20. try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
    21. synchronized (objectLockA){
    22. System.out.println(Thread.currentThread().getName()+"\t"+"B-------已经获得A");
    23. }
    24. }
    25. },"B").start();
    26. }
    27. }
    28. 复制代码

    8.2 如何排查死锁

    1、纯命令

    1. jps -l
    2. jstack 进程编号
    3. 复制代码

    2、图形化

    jconsole
  • 相关阅读:
    UDP网络通信收发包原理即代码
    尚品甄选2023全新SpringBoot+SpringCloud企业级微服务项目
    嵌入式IDE(2):KEIL中SCF分散加载链接文件详解和实例分析
    简单聊聊羊了个羊
    鸿蒙HarmonyOS实战-Stage模型(信息传递载体Want)
    Scrapy-reids-概念
    一行代码解决WINFORM因为系统放大导致的布局混乱
    Stable Diffusion WebUI详细使用指南
    【牛客】SQL125 得分不小于平均分的最低分
    最佳实践| 一文读懂《MongoDB 使用规范及最佳实践》原理
  • 原文地址:https://blog.csdn.net/BASK2311/article/details/127595568