JUC高并发编程(锁)

1、Lock

在这里插入图片描述

2、synchronized与Lock的区别

  1. 首先synchronized是java内置关键字(JVM层),Lock是个java类(API层);
  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锁适合代码少量的同步问题。

3、synchronized的使用方法(8种锁的案例实际体现的3个地方)

  1. 修饰实例方法,作用于当前实例,进入同步代码前需要先获取实例的锁。
  2. 修饰静态方法,作用于类的Class对象,进入修饰的静态方法前需要先获取类的Class对象的锁。
  3. 修饰代码块,需要指定加锁对象(记做lockobj),在进入同步代码块前需要先获取lockobj的锁。

4、悲观锁和乐观锁

4.1、悲观锁

  1. 在获取数据的时候会先加锁,确保数据不会被别的线程修改。

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

  3. 适合写操作多的场景,先加锁可以保证写操作时数据正确。

  4. 显式的锁定之后再操作同步资源。

4.2、乐观锁

  1. 乐观锁在使用数据时不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据;如果这个数据没有被更新,当前线程将自己修改的数据成功写入;如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作;乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。

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

  3. 乐观锁则直接去操作同步资源,是一种无锁算法。

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

     1. 采用版本号机制
     2. CAS(Compare-and-Swap,即比较并替换)算法实现
    

5、ReentrantLock

5.1、synchronized的局限性

  synchronized是java内置的关键字,它提供了一种独占的加锁方式。synchronized的获取和释放锁由jvm实现,用户不需要显示的释放锁,非常方便,然而synchronized也有一定的局限性,例如:

  1. 当线程尝试获取锁的时候,如果获取不到锁会一直阻塞,这个阻塞的过程,用户无法控制
  2. 如果获取锁的线程进入休眠或者阻塞,除非当前线程异常,否则其他线程尝试获取锁必须一直等待

  JDK1.5之后发布,加入java.util.concurrent包。包内提供了Lock类,用来提供更多扩展的加锁功能。Lock弥补了synchronized的局限,提供了更加细粒度的加锁功能。

5.2、 可重入锁(递归锁):

  指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。如果是1个有 synchronized 修饰的递归调用方法,程序第2次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚。所以Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。

Synchronized可重入的实现原理:
  1. 每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。

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

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

  4. 当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。

5.3、可中断锁

  可中断锁是子线程在获取锁的过程中,是否可以相应线程中断操作。synchronized是不可中断的,ReentrantLock是可中断的。

5.4、公平锁和非公平锁

  公平锁是指多个线程尝试获取同一把锁的时候,获取锁的顺序按照线程到达的先后顺序获取,而不是随机插队的方式获取。synchronized是非公平锁,而ReentrantLock是两种都可以实现,不过默认是非公平锁。

5.5、ReentrantLock的使用

  1. 创建锁:ReentrantLock lock = new ReentrantLock()
  2. 获取锁:lock.lock()
  3. 释放锁:lock.unlock()
  4. ReentrantLock锁申请等待限时:tryLock()
    synchronized关键字获取锁的过程中,只能等待其他线程把锁释放之后才能够有机会获取到锁。
    ReentrantLock提供了获取锁限时等待的方法tryLock(),可以选择传入时间参数,表示等待指定的时间,无参则表示立即返回锁申请的结果:true表示获取锁成功,false表示获取锁失败。
  5. ReentrantLock其他常用的方法
    isHeldByCurrentThread:实例方法,判断当前线程是否持有ReentrantLock的锁。
    获取锁的4种方法对比
获取锁的方法是否立即响应(不会阻塞)是否响应中断
lock()××
lockInterruptibly()×
tryLock()×
tryLock(long timeout, TimeUnit unit)×

5.6、总结

  1. ReentrantLock可以实现公平锁和非公平锁。
  2. ReentrantLock默认实现的是非公平锁。
  3. ReentrantLock的获取锁和释放锁必须成对出现,锁了几次,也要释放几次。
  4. 释放锁的操作必须放在finally中执行。
  5. lockInterruptibly()实例方法可以相应线程的中断方法,调用线程的interrupt()方法时,lockInterruptibly()方法会触发InterruptedException异常。
  6. 关于InterruptedException异常说一下,看到方法声明上带有 throws InterruptedException,表示该方法可以相应线程中断,调用线程的interrupt()方法时,这些方法会触发InterruptedException异常,触发InterruptedException时,线程的中断中断状态会被清除。所以如果程序由于调用interrupt()方法而触发InterruptedException异常,线程的标志由默认的false变为ture,然后又变为false。
  7. 实例方法tryLock()会尝试获取锁,会立即返回,返回值表示是否获取成功。
  8. 实例方法tryLock(long timeout, TimeUnit unit)会在指定的时间内尝试获取锁,指定的时间内是否能够获取锁,都会返回,返回值表示是否获取锁成功,该方法会响应线程的中断。

6、死锁

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

6.1、 产生死锁主要原因

  1. 系统资源不足
  2. 进程运行推进的顺序不合适
  3. 资源分配不当

6.2、 如何排查死锁

  1. 纯命令
jps -l
jstack 进程编号
  1. 图形化
jconsole工具
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

HEU_THY

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

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

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

打赏作者

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

抵扣说明:

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

余额充值