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


    文章目录:

    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接口的实现类都是悲观锁。

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

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

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

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

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

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

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

    2.synchronized的8锁案例

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

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

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

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

     

    1. package com.juc.lock;
    2. import java.util.concurrent.TimeUnit;
    3. /**
    4. *
    5. */
    6. class Phone {
    7. public void sendEmail() {
    8. synchronized (this) {
    9. System.out.println("-----发送邮件");
    10. }
    11. }
    12. public void sendSMS() {
    13. synchronized (this) {
    14. System.out.println("-----发送短信");
    15. }
    16. }
    17. }
    18. public class Lock8 {
    19. public static void main(String[] args) {
    20. Phone phone = new Phone();
    21. new Thread(phone::sendEmail, "a").start();
    22. try {
    23. TimeUnit.MILLISECONDS.sleep(200);
    24. } catch (InterruptedException e) {
    25. e.printStackTrace();
    26. }
    27. new Thread(phone::sendSMS, "b").start();
    28. }
    29. }

     

     

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

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

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

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

    1. package com.juc.lock;
    2. import java.util.concurrent.TimeUnit;
    3. /**
    4. *
    5. */
    6. class Phone {
    7. public void sendEmail() {
    8. synchronized (this) {
    9. try {
    10. TimeUnit.SECONDS.sleep(2);
    11. } catch (InterruptedException e) {
    12. e.printStackTrace();
    13. }
    14. System.out.println("-----发送邮件");
    15. }
    16. }
    17. public void sendSMS() {
    18. synchronized (this) {
    19. System.out.println("-----发送短信");
    20. }
    21. }
    22. }
    23. public class Lock8 {
    24. public static void main(String[] args) {
    25. Phone phone = new Phone();
    26. new Thread(phone::sendEmail, "a").start();
    27. try {
    28. TimeUnit.MILLISECONDS.sleep(200);
    29. } catch (InterruptedException e) {
    30. e.printStackTrace();
    31. }
    32. new Thread(phone::sendSMS, "b").start();
    33. }
    34. }

     

  • 相关阅读:
    javaee之黑马乐优商城3
    简单说说量化交易接口有哪些用途?
    Eclipse中导入外部jar包
    Html总结---持续更新中 2022.8.4
    算法基础 1.2 归并排序
    WPF真入门教程31--WPF版房屋租售系统
    【pygame游戏】用Python实现一个蔡徐坤大战篮球的小游戏,可还行?【附源码】
    linux运维笔记:ssh服务认证类型
    树洞外链网盘系统php源码去除底部版权优化版
    旅游卡小程序开发搭建
  • 原文地址:https://blog.csdn.net/java_lujj/article/details/126816622