带你彻底了解多线程锁策略(未完待续)

目录

1.乐观锁&悲观锁

2.轻量级锁&重量级锁

3.自旋锁&挂起等待锁

4.读写锁&普通互斥锁

5.公平锁&非公平锁

6.可重入锁&不可重入锁

7.CAS


1.乐观锁&悲观锁

   乐观锁是一种乐观思想,假定当前环境是读多写少,遇到并发写的概率比较低,读数据时认为别的线程不会正在进行修改(所以没有上锁)。写数据时,判断当前与期望值是否相同,如果相同则进行更新(更新期间加锁,保证是原子性的)。在获取这个锁的时候预期这个锁竞争不太激烈,那么就可以先不加锁,或者少加锁(有真实的竞争再来加锁)。

Java中的乐观锁: CAS,比较并替换,比较当前值(主内存中的值),与预期值(当前线程中的值,主内存中值的一份拷贝)是否一样,一样则更新,否则继续进行CAS操作

   悲观锁是一种悲观思想,即认为写多读少,遇到并发写的可能性高,每次去拿数据的时候都认为其他线程会修改,所以每次读写数据都会认为其他线程会修改,所以每次读写数据时都会上锁。其他线程想要读写这个数据时,会被这个线程block,直到这个线程释放锁然后其他线程获取到锁。在获取这个锁的时候预期这个锁的竞争非常激烈,那么就必须先加锁再执行任务。

Java中的悲观锁: synchronized修饰的方法和方法块、ReentrantLock。

2.轻量级锁&重量级锁

   轻量级锁是JDK6时加入的一种锁优化机制: 轻量级锁是在无竞争的情况下使用CAS操作去消除同步使用的互斥量。轻量级是相对于使用操作系统互斥量来实现的重量级锁而言的。轻量级锁在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。如果出现两条以上的线程争用同一个锁的情况,那轻量级锁将不会有效,必须膨胀为重量级锁。加锁过程比较简单,消耗资源比较少,典型就是用户态的一些加锁操作。

   重量级锁是一种称谓: synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的,监视器锁本身依赖底层的操作系统的 Mutex Lock来实现。操作系统实现线程的切换需要从用户态切换到核心态,成本非常高。这种依赖于操作系统 Mutex Lock来实现的锁称为重量级锁。为了优化synchonized,引入了轻量级锁,偏向锁。加锁过程比较复杂,消耗资源比较多,典型就是内核态的一些加锁操作。

Java中的重量级锁: synchronized

3.自旋锁&挂起等待锁

   自旋锁是一种技术: 为了让线程等待,我们只须让线程执行一个忙循环(自旋)。不停的检查锁是否被释放,如果一旦锁被释放那么就立即获取锁资源。

自旋锁是一种典型的轻量级锁的具体实现

自旋锁的优点: 避免了线程切换的开销。挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给Java虚拟机的并发性能带来了很大的压力。

自旋锁的缺点: 占用处理器的时间,如果占用的时间很长,会白白消耗处理器资源,而不会做任何有价值的工作,带来性能的浪费。因此自旋等待的时间必须有一定的限度,可以通过控制自旋的次数来提高效率,并且避免系统资源的过度浪费。

Java中的自旋锁: CAS操作中的比较操作失败后的自旋等待。

   挂起等待锁:不主动询问锁资源,而是让系统调度去参与锁竞争。

   1 通过阻塞和就绪状态的切换来获取锁资源

   2 之前的锁如果释放,没有办法立马知道

   3 是通过系统内核来处理

挂起等待锁是一种典型的重量级锁的具体实现

4.读写锁&普通互斥锁

   读写锁是一种技术: 通过ReentrantReadWriteLock类来实现。为了提高性能, Java 提供了读写锁,在读的地方使用读锁,在写的地方使用写锁,灵活控制,如果没有写锁的情况下,读是无阻塞的,在一定程度上提高了程序的执行效率。读写锁分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由 jvm 自己控制的。

读的时候加读锁(共享锁),多个锁可以共存,同时加多个读锁是互不影响的。

写的时候加写锁(排他锁),只能有一个写锁在执行任务,和别的锁是冲突的。

写锁和写锁不可以共存

写锁和读锁不可以共存

读锁和读锁可以共存

Java中的读写锁:ReentrantReadWriteLock   

   互斥锁与悲观锁、独占锁同义,表示某个资源只能被一个线程访问,只能一个线程释放锁之后,别的线程再来竞争。

读-读互斥

读-写互斥

写-读互斥

写-写互斥

Java中的同步锁: synchronized

5.公平锁&非公平锁

   公平锁是一种思想: 多个线程按照申请锁的顺序来获取锁。在并发环境中,每个线程会先查看此锁维护的等待队列,如果当前等待队列为空,则占有锁,如果等待队列不为空,则加入到等待队列的末尾,按照FIFO的原则从队列中拿到线程,然后占有锁。讲究先来后到,先排队的线程先获取到锁,后排队的线程后获取到锁。

   非公平锁是一种思想: 线程尝试获取锁,如果获取不到,则再采用公平锁的方式。多个线程获取锁的顺序,不是按照先到先得的顺序,有可能后申请锁的线程比先申请的线程优先获取锁。多个线程去竞争,谁抢到就是谁的。

Java中的非公平锁:synchronized是非公平锁,ReentrantLock可以通过构造函数指定该锁是公平的还是非公平的,默认是非公平的。

6.可重入锁&不可重入锁

   可重入锁是一种技术: 任意线程在获取到锁之后能够再次获取该锁而不会被锁所阻塞,对一把锁来连续加锁多次,而不会造成死锁。(加锁多次,解锁也要多次,否则别的线程获取不了了)。

可重入锁的原理: 通过组合自定义同步器来实现锁的获取与释放。

再次获取锁:识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取。获取锁后,进行计数自增,

释放锁:释放锁时,进行计数自减。

Java中的可重入锁: ReentrantLock、synchronized修饰的方法或代码段。

   不可重入锁意味着对一把锁连续加锁多次,造成死锁。

7.CAS

CAS(Compare-and-Swap),即比较并替换,是一种实现并发算法时常用到的技术,Java并发包中的很多类都使用了CAS技术。

   在这个机制中有三个核心的参数:

  • 主内存中存放的共享变量的值:V(一般情况下这个V是内存的地址值,通过这个地址可以获得内存中的值)
  • 工作内存中共享变量的副本值,也叫预期值:A
  • 需要将共享变量更新到的最新值:B

自旋就是在CAS外面包了一个循环,让CAS在循环中不停的执行 

CAS操作直接修改的就是内存中的值,每次都会去读,去比较,去修改指定内存地址的值,从而保证了原子性的操作。

CAS的缺点:

CAS虽然很高效的解决了原子操作问题,但是CAS仍然存在三大问题。

  1. 循环时间长开销很大。
  2. 只能保证一个共享变量的原子操作。
  3. ABA问题。

循环时间长开销很大:我们可以看到getAndAddInt方法执行时,如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。

只能保证一个共享变量的原子操作:当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。

什么是ABA问题?ABA问题怎么解决?

CAS 的使用流程通常如下:1)首先从地址 V 读取值 A;2)根据 A 计算目标值 B;3)通过 CAS 以原子的方式将地址 V 中的值从 A 修改为 B。

但是在第1步中读取的值是A,并且在第3步修改成功了,我们就能说它的值在第1步和第3步之间没有被其他线程改变过了吗?

如果在这段期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过。这个漏洞称为CAS操作的“ABA”问题。Java并发包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性。因此,在使用CAS前要考虑清楚“ABA”问题是否会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更高效。或者我们可以给预期值加一个属性,记录一下修改的次数(版本号)。

8.总结

1.问:可重入锁如果加了两把,但是只释放了一把会出现什么问题?

答:程序卡死,线程不能出来,也就是说我们申请了几把锁,就需要释放几把锁。

2.问:如果只加了一把锁,释放两次会出现什么问题?

答:会报错,java.lang.IllegalMonitorStateException。

3.锁消除是一种优化技术: 就是把锁干掉。当Java虚拟机运行时发现有些共享数据不会被线程竞争时就可以进行锁消除。

那如何判断共享数据不会被线程竞争?

利用逃逸分析技术:分析对象的作用域,如果对象在A方法中定义后,被作为参数传递到B方法中,则称为方法逃逸;如果被其他线程访问,则称为线程逃逸。

在堆上的某个数据不会逃逸出去被其他线程访问到,就可以把它当作栈上数据对待,认为它是线程私有的,同步加锁就不需要了。

4.锁粗化是一种优化技术: 如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作都是出现在循环体体之中,就算真的没有线程竞争,频繁地进行互斥同步操作将会导致不必要的性能损耗,所以就采取了一种方案:把加锁的范围扩展(粗化)到整个操作序列的外部,这样加锁解锁的频率就会大大降低,从而减少了性能损耗。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

北~笙

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

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

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

打赏作者

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

抵扣说明:

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

余额充值