首页 > 编程语言 > 详细

java并发(一)--锁归纳

时间:2020-12-08 09:36:11      阅读:22      评论:0      收藏:0      [点我收藏+]

info

java中锁的产生,是由于硬件的发展,以及多核处理器的产生。
在单机模式下,内存区域是共享的,早期一个处理器,所有线程都需要排队处理,也就意味着在任何一个时间点,有且仅有一个线程在执行。在这种串行模式下,是没有并发问题的。但是随着硬件技术的不断发展,处理器更新为多核,这也就意味着同一个时间点,有多个线程同时在被处理,当这多个线程操作同一块内存时,就会出现数据一致性的问题。

单机模式

可见性、原子性、有序性

并发实质是对可见性、原子性、有序性的处理。导致可见性bug的原因是缓存,导致有序性bug的原因是编译优化。对此,java本身提供了volatile、synchronized和final三个关键字和happens-before规则,来解决上述问题。

  • volatile提供了内存屏障以及数据及时从强制刷新到内存。
  • synchronized在jvm层提供了锁机制,通过对象头中标记完成线程的阻塞。
  • final则是告诉编译器,对应变量可以可劲优化,因为变量不会改变。

Happens-Before的7个规则:

  1. 程序次序规则:在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作。准确地说,应该是控制流顺序而不是程序代码顺序,因为要考虑分支、循环等结构。
  2. 管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调的是同一个锁,而"后面"是指时间上的先后顺序。
  3. volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里的"后面"同样是指时间上的先后顺序。
  4. 线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作。
  5. 线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。
  6. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。
  7. 对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。

Happens-Before的1个特性:传递性。

内存模型

下图是jdk1.8之后的内存模型。
技术分享图片

synchronized

底层是依赖JVM来实现,通过对象头中添加标记,来保证线程的阻塞。
当多个线程同时请求同一个对象的锁时,通过Contention List、Entry List、Wait Set来完成线程的阻塞。
自旋锁:在线程进入Contention List时,首先会进行自旋尝试获取锁,不成功进入等待队列。这个时候,自旋锁的线程会和entry list中的ready线程一起抢占锁,导致已经等待的线程不公平。但是由于自旋可以减小线程在用户态和内核态的频繁切换,提高效率。
偏向锁:为了解决在无竞争情况下锁性能问题。前提是锁必须是可重入锁,已经获得该对象的锁的线程再次进入锁的时候,通过已有变量直接进入。(上述的队列会出现CAS操作,例如进入Contention List的时候)。

lock

底层是直接依赖处理器来完成。所有请求线程在一个CLH队列中,当一个线程执行完毕(lock.unlock())时会激活自己的后继节点,但正在执行的线程并不在队列中,而那些等待执行的线程全部处于阻塞状态,等待线程的显式阻塞是通过调用LockSupport.park()完成,而LockSupport.park()则调用sun.misc.Unsafe.park()本地方法,再进一步,HotSpot在Linux中中通过调用pthread_mutex_lock函数把线程交给系统内核进行阻塞。

加锁:

tryAccquire来完成锁竞争
state状态值表示锁是否被占用,以及支持锁的重入和偏向(Sync.nonfairTryAcquire)
通过将调用线程封装成链表,通过前继节点来判断当前节点是否应该被阻塞。节点保存了当前线程的状态。

解锁:

当锁被释放release()的时候,会触发链表的head线程来争取锁。但是这个竞争过程也可能被刚进入的线程提前获取锁。
解锁的时候通过tryRelease()方法来讲state置为0,由于没有其他线程,所以不需要CAS。

condition

信号量,用于多线程之间的线程编排(线程阻塞,唤醒等依赖于lock),效力类似于wait()/notify()。
相比较wait()/notify(),condition更加灵活,能更加精准控制线程。但是当线程编排业务复杂时候,condition编写起来就会比较复杂。

//获取信号量
Condition condition = lock.newCondition();

分布式锁

redis

while(!redisTempLate.setNX(key,value,time)){}

通过自旋来阻塞线程获取锁。
对key添加过期时间来避免死锁。
对于公平性,可以通过FIFo的队列来管理线程
集群情况下,会出现数据的一致性问题,集群情况下采用Redlock算法。

zookeeper

大致思想即为:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题
节点存储主机信息,可以保证数据的可重入。
节点是有序的,可以通过节点的顺序来保证锁的公平性,通过上述的主机信息来通知线程。
对zk的加锁解锁操作,都需要leader来完成,以便保证集群数据的一致性。(leader会将数据同步给从节点)
性能上比redis差很多,但是可靠性比redis好。

参考目录:
https://blog.csdn.net/weixin_37817685/article/details/89071055 [内存模型]
https://blog.csdn.net/chen77716/article/details/6618779 [synchronize]
https://blog.csdn.net/chen77716/article/details/6641477 [lock]
https://blog.csdn.net/huyaowei789/article/details/87873977 [分布式锁]

java并发(一)--锁归纳

原文:https://www.cnblogs.com/ElliottX4/p/14100476.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!