1.常见的锁
1.自旋锁:不放弃cpu执行事件,不停地尝试对资源进行访问
2.乐观锁:假设数据没有被更改,在修改数据之前,先读数据,如果数据和之前的不一致,则放弃修改去读取最新数据,然后再尝试修改
3.悲观锁:假设数据一定会被修改,同步所有对数据的访问,从读数据就开始同步
4.独享锁(写):给资源加上写锁,其他线程无法访问,也无法加锁(单写)
5.共享锁(读):给资源加上读锁,其他线程无法加锁,可以读,无法写(多读)
6.可重入锁,不可重入锁:当线程获取到锁时,再访问锁同步的其他内容,无需再次获取锁
7.公平锁,非公平锁:获取锁的顺序,是顺序还是非顺序,先来先得即是公平,后来先至非公平
2.synchronized
1.概念:属于最基本的线程通信机制,基于对象监视器实现,java中每一个对象都与一个监视器相关联,一个线程可以锁定,也可以解锁,一次只有一个线程可以锁定监视器,视图锁定监视器的线程都会进入阻塞,直到再次获得监视器的锁
2.特性:可重入,独享,悲观锁,保证可见性
3.锁的范围:类锁,对象锁,锁消除(比如在方法中使用stringbuffer,会消除stringbuffer的锁,因为方法中是线程独享的,不会有线程安全问题,是java的一些优化手段,减少锁的消耗),锁粗化(例如在for循环内,对i++加锁,java实际运行会扩大锁的范围到for循环外,因为循环加锁没必要,消耗性能,在外面加锁也是一个效果)
3.synchronized实现原理:
几个概念,可以用数据库来类比
堆:数据库
对象:表
头:字段
头这个字段的数据类型是json,json里存有很多属性,比如state属性:01unlock 00轻量级锁 10重量级锁 11被标记回收
具体争抢过程:无锁-偏向锁(主要针对单线程还加了同步关键字,因为java认为大部分时候都是单线程操作,无需加锁,一旦有了线程争抢,就升级轻量级锁 )-轻量级锁-重量级锁
从jdk6开始对锁进行了优化,
偏向锁:头字段中有一个字段表示是否开启偏向锁,另一个字段保存获取偏向锁的线程对象,默认开启偏向锁,第一个线程进入,先看看有没有开启偏向锁,如果开启了,则去将自己设置为偏向锁的所有者,第二个线程来,发现偏向锁有了主人,则会放弃获取锁,此时偏向锁变成关闭状态,后续线程则不会走偏向锁,升级为去获取轻量级锁....
第一个线程获取对象的锁,会尝试去修改状态位为00轻量级锁,修改方式为cas,多个线程只有一个线程可以修改成功,修改不成功的则进入自旋+cas方式尝试修改,但是会限制尝试次数,如果多次尝试仍未获取锁,这些线程会进入对象监视器内,同时锁升级为重量级锁,每个对象都有一个对应的监视器,监视器中存储了锁的当前拥有者,和锁的状态,还有等待锁的线程集合,当线程释放锁之后,这些等待线程重现尝试去获取锁,如此循环.
总结:偏向锁本质就是无锁,jvm为了优化性能,默认是单线程操作,避免频繁使用锁,降低性能.我们平时也许在代码中加了关键字,但实际执行优化后,不一定真的加了锁,而是使用的偏向锁,只有当出现多个线程争抢,才会退出偏向锁,开始使用锁
轻量级锁和重量级锁主要区别是对象监视器出现协调资源争抢
4.Lock锁
1.和synchronized的区别:更加灵活的方式使用锁,提供了tryLock(),尝试一次,获取不到立刻返回,tryLock(1000,Timeunit.secrod),在1秒内不停尝试获取锁,超过时间则立刻返回
2.lock的lock()方法必须放在try中,unlock()放在finally中执行,同一个线程lock可以获取多次,同时也必须释放多次,否则其他线程仍获取不到锁.
3.lock可以使用响应中断的方式lockInterruptibly(),如果使用lock(),则使用Interruptibly()中断时,线程不会立即中断
4.lock锁主要两个实现,包括可重入锁和读写锁
5.读写锁是维护了关联的两个锁,读锁是允许多个线程同时拥有,写锁是独占锁.读写锁适合读多写少的场景,比如缓存
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
readWriteLock.readLock().lock();
readWriteLock.writeLock().lock();
readWriteLock.readLock().unlock();
6.当有读锁存在,写锁无法获取,读锁可以获取;当有写锁时,只有自己获取读锁,其他线程无法获取,这一过程也叫锁降级,是指在持有写锁的同时,获取读锁,随后释放写锁
7.可以使用读写锁解决缓存雪崩的情况,当读取缓存内容前获取读锁,如果有内容,直接返回内容,并释放锁,否则,将读锁释放,同时获取写锁,这样即使有很多线程想要直接去访问数据库,也只有一个线程能拿到锁去读数据库,然后将数据存入缓存,再次判断缓存是否有内容,其他等待线程就不会访问数据库,而是读取缓存内容,防止大量线程读取数据库,造成数据库奔溃
5.锁的本质--排队
1.同步方式:独享锁(单个队列窗口) 共享锁(多个队列窗口)
2.抢锁的方式:插队抢(非公平锁) 先到先得(公平锁)
3.没抢到锁的方式:快速尝试多次(CAS自旋) 阻塞等待
4.唤醒阻塞方式:叫醒全部 叫醒下一个
6.AQS抽象队列同步器
抽象了资源的获取,释放逻辑
核心3大件就是state状态位:表示当前资源被占用次数,占用次数为0代表未被占用;owner锁的拥有者;队列:阻塞获取资源的线程队列
AQS围绕这三个核心属性,抽象了8个方法,分别是独占资源的释放/尝试释放 和 获取/尝试获取,共享资源的释放/尝试释放 和 获取/尝试获取