Java——聊聊JUC中的锁(synchronized & Lock & ReentrantLock)

本文从乐观锁和悲观锁的概念出发,详细探讨了Java中`synchronized`的8种锁案例,分析了字节码层面的同步机制。通过实例展示了synchronized同步代码块、同步实例方法、同步静态方法的情况,以及锁的是什么。同时,讨论了公平锁和非公平锁,以及可重入锁的原理,包括隐式锁`synchronized`和显式锁`Lock`的重入特性。最后,文章提及了死锁的问题及其排查方法,为读者提供了深入理解Java并发编程的基础。
摘要由CSDN通过智能技术生成

文章目录:

1.从乐观锁和悲观锁开始说起

2.synchronized的8锁案例

2.1 第一种情况:两个线程锁的是同一个实例对象

2.2 第二种情况:第一个线程的逻辑中添加sleep睡眠

2.3 第三种情况:第二个线程执行的是无锁方法

2.4 第四种情况:两个线程锁的是两个不同的实例对象

2.5 第五种情况:两个线程锁的是同一个类对象

2.7 第七种情况:一个线程锁实例对象,一个线程锁类对象

3.字节码角度分析synchronized

3.1 synchronized同步代码块

3.2 synchronized同步实例方法

3.3 synchronized同步静态方法

3.4 synchronized锁的是什么?

5.1 可重入锁之隐式锁synchronized

5.2 可重入锁之显式锁Lock

1.从乐观锁和悲观锁开始说起

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

悲观锁的实现方式:① synchronized关键字;

② Lock接口的实现类都是悲观锁。

适合写操作多的场景,先加锁可以保证写操作时数据正确。显示的锁定之后再操作同步资源。

public synchronized void method() {
    //加锁之后的业务逻辑
}

Lock lock = new ReentrantLock();

public void method2() {
    lock.lock();
    try {
        //加锁之后的业务逻辑
    } finally {
        lock.unlock();
    }
}
  • 乐观锁:乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作

                     乐观锁的实现方式:① 版本号机制Version。 (只要有人提交了就会修改版本号,可以解决ABA问题)

ABA问题:再CAS中想读取一个值A,想把值  A变为C,不能保证读取时的A就是赋值时的A,中间可能有个线程将A变为B再变为A。

解决方法:Juc包提供了一个AtomicStampedReference,原子更新带有版本号的引用类型,通过控制版本值的变化来解决ABA问题。
② 最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。

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

AtomicInteger atomicInteger = new AtomicInteger(1);
atomicInteger.incrementAndGet();

2.synchronized的8锁案例

首先,我们可以看一下阿里巴巴Java开发手册中,关于锁的强制性要求。

2.1 第一种情况:两个线程锁的是同一个实例对象

这里我能使用 Lambda 表达式的原因是,Phone类中的这两个实例方法是无参、无返回值的,和Runnable中的run方法一致,所以直接方法引用是OK的。

两个线程锁的都是我 new 的同一个对象 phone,所以当第一个线程去发邮件的时候就拿到了 phone 对象这把锁,此时第二个线程就拿不到了,只能等待第一个线程执行完释放锁,它才可以去发短信。

package com.juc.lock;

import java.util.concurrent.TimeUnit;

/**
 *
 */
class Phone {
    public void sendEmail() {
        synchronized (this) {
            System.out.println("-----发送邮件");
        }
    }

    public void sendSMS() {
        synchronized (this) {
            System.out.println("-----发送短信");
        }
    }
}

public class Lock8 {

    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(phone::sendEmail, "a").start();

        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(phone::sendSMS, "b").start();
    }
}

2.2 第二种情况:第一个线程的逻辑中添加sleep睡眠

和第一种情况不同的是:当第一个线程拿到 phone 对象锁之后,在发邮件的过程中,sleep睡眠了2秒。但是执行结果和第一种情况是一样的。

原因就是 sleep 方法并不会释放锁,只是让线程暂定一段时间,一段时间过后线程照常执行(不要interrupt打断。。。)。

某一个时刻内,只能有唯一的一个线程去访问这些针对于实例对象的synchronized方法, 锁的是当前对象this ,被锁定后,其它的线程都不能 进入到当前对象的其他synchronized方法。

package com.juc.lock;

import java.util.concurrent.TimeUnit;

/**
 *
 */
class Phone {
    public void sendEmail() {
        synchronized (this) {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("-----发送邮件");
        }
    }

    public void sendSMS() {
        synchronized (this) {
            System.out.println("-----发送短信");
        }
    }
}

public class Lock8 {

    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(phone::sendEmail, "a").start();

        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(phone::sendSMS, "b").start();
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值