Java中的锁机制

乐观锁

乐观锁认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作是没有事情的。(自旋锁)

悲观锁

悲观锁认为对于同一个数据的并发操作,一定会发生修改,哪怕没有修改,也会认为修改。因此,对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出现问题。

悲观锁适合写操作多的场景,乐观锁适合读操作的场景,不加锁会带来大量性能的提升。(Synchronized)

可重入锁

又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。对于JavaReentrantLock而言,他的名字就可看出是一个可重入锁,其名字是Reentrant Lock重新进入锁。

对于Synchronized而言,也是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁。

读写锁

1.多个读者可以同时进行读

2.写着必须互斥

3.写着优先于读者
(ReentrantReadWriteLock)

分段锁

分段锁并非一种实际的锁,而是一种思想,用于将数据分段,并在每个分段上都会单独加锁,把锁进一步细粒度化,以提高并发效率。(concurrenthashmap)

自旋锁

所谓自选其实指的就是自己重试,当线程抢锁失败后,重试几次,要是抢到锁了就继续,要是抢不到就阻塞线程。

由此可见,自旋锁是比较消耗CPU的,因为要不断循环重试,不会释放CPU资源,另外,加锁时间普遍较短的场景非常适合自旋锁,可极大提高锁的效率。(cas)

共享锁/独占锁

共享锁是指该锁可被多个线程所持有,并发访问共享资源

独占锁也叫互斥锁,是指该锁一次只能被一个线程所持有。

对于java ReentrantLock,Synchronized而言,都是独享锁,但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其余都是独享锁

非公平锁/公平锁

公平锁是指按照请求锁的顺序分配,拥有稳定获得锁的机会,但是性能可能比非公平锁低。

非公平锁是指不按照请求锁的顺序分配,不一定拥有获得锁的机会,但是性能可能比公平锁高。

对于synchronized而言,是一种非公平锁。

ReentrantLock默认是非公平锁,但是DF底层可以通过AQS来实现线程调度,所以可以使其变成公平锁

偏向锁/轻量级锁/重量级锁

偏向锁

偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低锁的代价

轻量级:自旋

轻量级锁是指当锁是偏向锁的时候,此时又有一个线程访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。

重量级锁:需要操作系统调度

重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。高并发情况下,出现大量线程自旋获得锁,对CPU销毁较大,升级为重量级锁后,获取不到锁的线程将阻塞,等待操作系统的调度

AQS实现原理

在内部有一个state变量表示锁是否使用,初始化0,在多线程条件下,线程

要执行临界区的代码,必须首先获取state,某个线程获取成功之后,state加1,

其他线程再获取的话由于共享资源已被占用,所以会到FIFO等待队列去等待,等占有state的线程执行完临界区的代码释放资源(state-1)后,会唤醒FIFO中的下一个等待线程(head的下一个节点)去获取state

state由于是多线程共享变量,所以必须定义成volatile,以保证state的可见性,同时虽然volatile能保证可见性,但不能保证原子性,所以AQS提供了对state的原子方法,保证了线程安全。

另外AQS中实现的FIFO队列其实是双向链表实现的,head节点代表当前占用的线程,其他节点由于暂时获取不到锁所以依次排队等待锁释放。

队列由Node对象组成,Node是AQS中的内部类。

AQS的锁模式分为:独占和共享

独占锁:每次只能有一个线程持有锁,比如ReentrantLock就是以独占方式实现的互斥锁

共享锁:允许多个线程同时获取锁,并发访问共享资源,比如ReentrantReadWriteLock

ConcurrentHashMap

ConcurrentHashMap 同步容器类是 Java 5 增加的一个线程安全的哈希

表。对与多线程的操作,介于 HashMap 与 Hashtable 之间。内部采用“锁分

段”机制(jdk8 弃用了分段锁,使用 cas+synchronized)替代 Hashtable 的独

占锁。进而提高性能。

放弃分段锁的原因

1.加入多个分段锁浪费内存空间

2.生产环境中,map在放入时竞争同一个锁的概率非常小,分段锁反而会造成更新等操作的长时间等待。

jdk8放弃了分段锁而是用了Node锁,降低了锁的粒度,提高性能,并使用CAS操作来确保Node的一些操作的原子性,取代了锁。

put时首先通过hash找到对应链表过后,查看是否是第一个Node,如果是,直接用CAS原则插入,无需加锁,

然后,如果不是链表第一个Node,则直接用链表第一个Node加锁,这里加锁的是synchronized.

ConcurrentHashMap不支持存储null键和null值

ConcurrentHashMap不能put null是因为无法分辨是key没找到的null还是有key值为null,这是多线程里面模糊不清的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值