很久就想写锁这个主题了,但是一直不敢写,因为自己感觉我的能力还是不足以驾驭锁,没法覆盖锁的内容,后面一想我就先以我粗浅的知识说说自己心中的锁,以后有更多的感悟再加上(当时面试阿里,面试官第一个问题就是说说对锁的认识,不限于语言,不限于软硬件,把你自己知道的说出来,现在回顾当时的回答是多么的2 young 2 simple。哈哈^_^,后来一聊,人家是专门写jvm gc的)。
我现在就给锁下一个定义:
锁:用来保护共享数据,实现原子性的一种机制。一方面软件上根据不同的实现和不同的场景分了多种锁;另一方面硬件提供了一些具有线性一致性的指令,比如atomic read-write,atomic swap,test-and-set,操作系统基于这些指令建立mutual exclusion、 semaphores 等锁结构。
架构设计上的锁:单机版,分布式版。
根据不同场景下锁的分类:
竞争激烈方面:悲观锁,乐观锁。具体来说,假定锁竞争激烈,处于数据安全考虑,使用悲观锁;假定锁竞争不激烈,使用乐观锁。
根据锁住同步资源失败后,线程是否要阻塞:如果阻塞就是内置锁,如果不阻塞就是自旋锁。
根据多个线程竞争同步资源时具体流程差异:
1不用锁资源,多个线程中只有一个能访问资源,其他线程会重试。该类称为无锁。
2同一个线程执行同步资源时自动获取资源。该类称为偏向锁。
3多个线程竞争同步资源时,没有获取到资源的线程会自旋等待锁释放。该类称为轻量级锁。
4当有多个线程竞争同步资源时,没有资源的线程阻塞等待唤醒。该类称为重量级锁。
根据判断多个线程竞争锁时要不要排队:如果多个线程间排队,该类称为公平锁。如果多个线程可先尝试插队,插队失败后再排队,该类称为非公平锁。
根据判断一个线程是否可以重复获取同一个锁:如果可以获取同一个锁,该类称为可重入锁,如果不能获取同一个锁,该类称为非重入锁。
根据判断多个线程间是否可以共享一个锁:如果可以共享,该类称为共享锁,如果不能,该类称为排它锁。
根据判断server实例是单例还是多例,多个线程竞争同步资源:如果是单例就是上面我们说的所有情况下的锁,如果是多例,则是分布式锁。
为什么现在的锁会如此错综复杂,我认为一方面是技术实现不同,二是关注的对象不同,上面根据不同的场景下对锁进行了简单的归类,实际上还有些是不包括的,比如读写锁,它的读锁时共享的,但是写锁是不共享的。
下面我就聊聊各种锁吧:
1乐观锁VS悲观锁。
悲观锁:
针对多个线程竞争同步资源的操作,悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改,现实社会中就是那种宁可我负别人,别人不能负我的那种。


乐观锁:
针对多个线程竞争同步资源的操作,乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作(例如报错或者自动重试),在现实社会中就是今朝有酒今朝醉的那种人。
实现乐观锁,悲观锁可查看我的另一篇博客(经过我考虑后,我把这篇博客作为总纲,然后围绕着锁这个概念具体分析锁的实现,由于锁的实现篇幅太长,技术点也很多,把内容填充在这里会显得臃肿),博客地址:
todo
实现乐观锁主要包含这两个步骤:冲突检测和数据更新。乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法,CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B),它的核心思想是通过比对内存值与预期值。简单来说cas操作可以减少加锁带来的开销,但是cas也是有缺陷的,如果多个线程间经常锁竞争,导致其一直自旋,带来资源浪费,另一方面会产生ABA问题,下面给出cas的缺陷具体描述文章链接。
todo

锁的实现:
用悲观锁概念实现的锁:synchronized关键字和Lock的实现类。
用乐观锁概念实现的锁:采用cas算法的实现类。
该分类下锁的适应场景:
悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。
乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。

2. 自旋锁 VS 适应性自旋锁
自旋锁:
自旋锁的实现原理为CAS,AtomicInteger中调用unsafe进行自增操作的源码中的do-while循环就是一个自旋操作,如果修改数值失败则通过循环来执行自旋,直至修改成功。
 适应性自旋锁:
自适应意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。
对于自旋锁的概念和自旋锁的实现请查看以下链接:
todo
3. 无锁 VS 偏向锁 VS 轻量级锁 VS 重量级锁
这四种锁是指锁的状态,专门针对synchronized的。
无锁在多个线程获取同步资源锁失败概率低的时候性能是最好的。
在大多数情况下,锁总是由同一线程多次获得,不存在多线程竞争,所以出现了偏向锁。
偏向锁通过对比Mark Word解决加锁问题,CAS操作只执行一次。而轻量级锁是通过用CAS操作和自旋来解决加锁问题,避免线程阻塞和唤醒而影响性能。重量级锁是将除了拥有锁的线程以外的线程都阻塞。
4. 公平锁 VS 非公平锁
5. 可重入锁 VS 非可重入锁
6. 排他锁(互斥锁/独享锁) VS 共享锁
什么叫排他锁:指该锁一次只能被一个线程所持有,如果线程T对数据A加上排它锁后,则其他线程不能再对A加任何类型的锁,同时只有线程T对数据A有读写权限。
什么叫共享锁:指该锁可被多个线程所持有,如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁,另一方面获得共享锁的线程只能读数据,不能修改数据。
排他锁与共享锁的实现请查看以下链接:

7分布式锁

 

使用锁所带来的问题 
使用锁之后可能带来的问题有三种:死锁,活锁,饥饿锁。
死锁是两个线程同时在请求对方占有的资源;
活锁是线程对任务的处理没有取得任何进展;
饥饿是一个线程在无限地等待其他线程占有的但是不会往外释放的资源。
死锁:
死锁的解决办法有:加锁顺序、加锁时限、死锁检测,具体可参考:
http://ifeve.com/deadlock-prevention/
活锁:线程对任务的处理丝毫取不到任何进展。
活锁的解决办法有:设置最大重试次数,超过该阈值后,监听自动停止;消息监听器监听到消息后,若不成功,则存表,不作回滚处理,
    事后再进行定时异步补偿处理;
饥饿:给线程设置了优先级,优先级低的线程始终得不到cpu资源没有执行的机会。
饥饿的解决办法有:在synchronized方法或者块中避免无限循环、采用线程默认的优先级。

既然引入锁有问题,那怎么解决(优化)该问题呢?
锁的优化
减小锁粒度,锁分离,锁粗化,锁消除

面试总结

1为啥要把aqs设计成模板方法模式?

2读写锁是怎么实现的?

3jvm是怎样优化synchronized?

跑题时间:今年的互联网圈比较“火”哈,知乎带头,什么美团,摩拜也紧跟其后,ofo更不用提了,公司算是黄了,可怜我的99元的身家性命打水漂了啊。人员优化不断,给互联网技术人员好好上了一课,无论你业务代码写得多流弊,公司砍掉业务线,你什么都不是,你只不过是一个高级点的奴隶罢了,我好几个小伙伴都是在找工作,但是面下来的结果是技术和钱是不成正比的,浅的人家2-3年的人就把活干了,深的人家一问只知道概念,具体怎么实现的要么不知道,要么看过忘记了,所以面试高级以上很难面,除非熟人对你知根知底的。我算是看透了,今年现在好好沉淀点技术,找准技术突破点成为有一技之长的技术人才是永恒之道。

参考博客:
https://tech.meituan.com/Java_Lock.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值