线程锁及锁的升降级

目录

一、Lock 简介、地位、作用

二、Lock 方法

1、lock()

2、tryLock()

3、lockInterruptibly()

4、unlock():解锁

5、interrupt()方法:

6、interrupted()方法:

7、isInterrupted()方法:

三、锁

1、锁的分类如图所示:

 2.乐观锁 悲观锁

3、可重入锁和非可重入锁,以 ReentrantLock 为例(重点)

4、公平锁和非公平锁

5、共享锁和排它锁

6.锁状态

6.1无锁

6.2偏向锁

6.3轻量级锁

6.4重量级锁


一、Lock 简介、地位、作用

1、锁是一种工具,用于控制对共享资源的访问;

2、Lock 和 synchronized,这两个是最常见的锁,他们都可以达到线程安全的目的,但是在使用上和功能上又有较大的不同;

3、Locak 并不是用来代替 synchronized 的,而是当使用 synchronized 不合适或不足以满足要求的时候,来提供高级功能的;

4、Lock 接口最常见的实现类是 ReentrantLock;

5、通常情况下,Lock只允许一个线程来访问这个共享资源。不过有的时候,一些特殊的实现可允许并发访问,比如 ReadWriteLock 里面的 ReadLock;

6、为什么 synchronized 不够用?

  1. 效率低:锁的释放情况少、试图获得锁时不能设定超时、不能中断一个正在试图获得锁的线程(当进入 synchronized 中的线程,如果发生异常,JVM 会让其释放锁);
  2. 不够灵活(读写锁更灵活):加锁和释放的时机单一,每个锁仅有单一的条件(某个对象),可能是不够的;
  3. 无法知道是否成功获取到锁。

二、Lock 方法

1、lock()

  1. lock() 就是最普通的获取锁。如果锁以被其他线程获取,则进行等待;
  2. Lock 不会像 synchronized 一样在异常时自动释放锁;
  3. 因此最佳实践是,在 finally 中释放锁,以保证发生异常时锁一定被释放

2、tryLock()

  1. tryLock() 用来尝试获取锁,如果当前锁没有被其他线程占用,则获取成功,并返回 true,否则返回 false;
  2. 该方法会立即返回,不会像 lock() 一样,拿不到锁就阻塞在那里;
  3. tryLock(long time , TimeUnit unit):超时时间内如果还没有获取到锁,返回 false,如果能获取到锁,就获取锁,并返回 true。

3、lockInterruptibly()

  1. lockInterruptibly() 相当于 tryLock(long time , TimeUnit unit) 把超时时间设置为无限。在等待锁的过程中,线程可以被中断。

4、unlock():解锁

5、interrupt()方法:

  1. 其作用是中断此线程,它可以中断使用 lockInterruptibly() 获取锁,并且正在运行的线程,也可以中断使用 lockInterruptibly() 等待获取锁的线程。但是 interrupt()不能中断使用 lock() 等待获取锁的线程,也不能中断使用 lock() 获取锁,并正在运行的线程,除非,这个线程运行到了 Thread.sleep(),他会抛出异常,而执行 finally 中的 unlock() 释放锁。

6、interrupted()方法:

  1. 作用是测试当前线程是否被中断(检查中断标志),返回一个boolean并清除中断状态,第二次再调用时中断状态已经被清除,将返回一个false。

7、isInterrupted()方法:

  1. 作用是测试此线程是否被中断 ,不清除中断状态。

三、锁

1、锁的分类如图所示:

 2.乐观锁 悲观锁

2.1 乐观锁是是什么

认为自己在处理操作时不会有其他线程打扰1,所以不会锁住被操作的对象

在更新时,去对比在我修改的数据期间的数据有没有被其他人修改过,如果没被改变过,就说明真的只有我们自己在操作,就去正常修改数据。

如果数据和一开始拿到的不一样,说明其他人修改了数据,不能用刚才更新过的数据,放弃、报错重试等策略。

乐观锁一般是利用CAS算法实现

乐观锁的典型就是原子类,并发容器。

2.2悲观锁

  1. 如果我不锁住这个资源,别人就会来争抢,就会造成数据结果错误,所以每次悲观锁为了确保结果的正确性,会在每次获取并修改数据时,把数据锁住,让别人无法访问该数据,这样就可以确保数据内容万无一失;
  2. Java 中悲观锁的实现就是 synchronized 和 Lock 相关类。

2.3两种锁的使用场景

1.悲观锁:适合并发写入多的情况,用于临界区吃锁时间比较长的情况,悲观锁可以避免大量无用自旋等消耗。

典型情况:

  1. 临界区有IO操作。
  2. 临界区代码复杂,循环量大
  3. 临界区竞争激烈

2.乐观锁,适合并发写入少,大部分是读取的场景,不加锁的能让读取性能大幅度提高。

3、可重入锁和非可重入锁,以 ReentrantLock 为例(重点)

  1)可重入锁

   同一个线程没有释放锁的情况下,重复获取自己所持有的锁。

  2)非可重入锁

    1、非重入锁是直接尝试获取锁,如果已经持有锁,不能获再次取锁;

    2、释放锁时也是直接将 status 置为0。

  3)可重入锁和非可重入锁区别:

    1、可重入锁在使用的时候一般是一个类当中有AB两个方法,而A和B都是有统一的一把锁,当实施A方法的时候就可以获得锁,但在A办法的所还没有全部释放的时候也可以直接使用B方法,而在这个时候也是可以获得这个锁的。
    2、不可重入锁也是指的是A和B两个方法,A和B可以获得统一的一把锁,而在A方法还没有释放的时候是没有办法使用B方法的,也就是说必须要等A释放之后才可以使用B方法。

4、公平锁和非公平锁

  1)公平的情况(以 ReentrantLock 为例)

    1、创建 ReentrantLock 时传入 true 参数即可;

    2、在线程1执行 unlock() 释放锁之后,由于此时线程2的等待时间最久,所以线程2先得到执行,然后是线程3、线程4。

  2)不公平情况(以 ReentrantLock 为例)

    1、创建  ReentrantLock 时传入 false 参数或者不传即可;

    2、如果在线程1释放锁的时候,线程5恰好去执行 lock(),就是插队了;

    3、线程5可以插队,直接拿到这把锁,也是 ReentrantLock 默认的公平策略,也就是“不公平”

优势劣势
公平锁各线程公平平等,每个线程在等待一段时间后,总有执行的机会更慢,吞吐量更小
不公平锁更快,吞吐量大有可能产生线程饥饿,也就是某些线程长时间内始终无法执行。

5、共享锁和排它锁

1、什么是共享锁和排它锁

  1)共享锁

    1. 共享锁,又称为读锁,获得共享锁之后,可以查看但无法修改和删除数据,其他线程此时也可以获取到共享锁,也可以查看但无法修改和删除数据。

  2)排它锁

    1. 排它锁,又称独占锁、独享锁。

  3)共享锁和排它锁的典型是读写锁 ReentrantReadWriteLock,其中读锁是共享锁,写锁是独享锁。

2、读写锁的作用

  1)在没有读写锁之前,我们假设使用 ReentrantLock,那么虽然我们保证了线程安全,但是也浪费了一定的资源:多个读操作同时进行,并没有线程安全问题;

  2)在读的地方使用读锁,在写的地方使用写锁,灵活控制,如果没有写锁的情况下,读是无阻塞的,提高了程序的执行效率。

3、读写锁的规则

  1)多个线程只申请读锁,都可以申请到;

  2)如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁;

  3)如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁;

  4)一句话总结:要么是一个或多个线程同时有读锁,要么是一个线程有写锁,但是两者不会同时出现(读读共享、其他都互斥(写写互斥、读写互斥、写读互斥))。

6.锁状态

在synchronized锁最初的实现方式是“阻塞或唤醒一个线程需要操作系统切换CPU状态来完成,这种状态需要耗费处理器时间,如果同步代码块中内容过于简单,这种切换的时间可能比用户代码执行时间还长”,这种方式就是synchronized实现同步最初的方式,这也是当初开发者诟病的地方,这也是在JDK6以前synchronized效率低下的原因,JDK6中为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”。

所以目前锁状态一种有四种,从级别由低到高依次是:无锁、偏向锁,轻量级锁,重量级锁,锁状态只能升级,不能降级。

6.1无锁

无锁是指没有对资源进行锁定,所有线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。无锁的特点是修改操作会在循环内进行,线程会不断尝试修改共享资源,如果没有冲突就修改成功并退出,否则就会继续循环尝试。如果多个线程修改同一个值,必定会有一个线程修改成功,而其他修改失败的线程就会不断重试,直到修改成功。

6.2偏向锁

偏向锁是指当一段同步代码一直被同一个线程所访问时,即不存在多个线程竞争时,那么该线程在后序访问时便会自动获得锁,从而降低获得锁带来的消耗,提高性能。

6.3轻量级锁

加锁机制尽可能不使用 mutex, 而是尽量在用户态代码完成. 实在搞不定了, 再使用 mutex.

  • 少量的内核态用户态切换.
  • 不太容易引发线程调度.

6.4重量级锁

加锁机制重度依赖了 OS 提供了 mutex

  • 大量的内核态用户态切换
  • 很容易引发线程的调度
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值