Synchronized和lock

Synchronized和lock的区别
第一个实现方式上:
Synchronized是java关键字,在jvm上实现,lock是一个接口
可重入lock实现是基于aqs抽象的队列同步器+lock接口实现的。
第二个锁释放方式上:Synchronized会自己释放,lock需要自己调用unlock,所以需要写在fially中。
第三个是否可中断等待:Synchronized不能等待中断只能等待其他线程释放锁,而lock可以主动中断等待。
第四个是能否知道是否已获取锁,Synchronized无法知道,lock锁可以使用trylock.
第五个,lock有读写锁的实现,可以提高并发读场景下的效率,读写锁还可以使用锁降级,读锁降级为写锁,在一边读一边写的情况下提高性能。
第六个在性能上,竞争不激烈情况下,差不多,但是竞争激烈情况下,此时Lock的性能要远远优于synchronized。在具体使用时要根据适当情况选择。
第七个锁的调度方式:synchronized使用Object对象本身的wait 、notify、notifyAll调度机制,而Lock可以使用Condition进行线程之间的调度,Condition可以指定唤醒等待的某一个线程。Condition:
第八个2种锁的锁机制不同:synchronized采用悲观锁机制,线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。Lock用的是乐观锁方式使用CAS操作,实现了独享锁和共享锁,比如readwritelock。
第九个,Synchronized是使用对象上进行加锁阻塞线程(对象的wait和notify方法),lock是通过阻塞线程的方式进行。(park unpark一个线程)

Synchronized实现原理:
synchronized修饰的方法在字节码中添加了一个ACC_SYNCHRONIZED的flags,同步代码块则是在同步代码块前插入monitorenter,在同步代码块结束后插入monitorexit。这两者的处理是分别是这样的:当线程执行到某个方法时,JVM会去检查该方法的ACC_SYNCHRONIZED访问标志是否被设置,如果设置了那线程会去获取这个对象所对应的monitor对象(每一个对象都有且仅有一个与之对应的monitor对象),获取成功后才执行方法体,方法执行完再释放monitor对象,在这一期间,任何其他线程都无法获得这个monitor对象。
在java6之前monitor对象的实现是依赖操作系统的互斥锁,涉及内核与用户态的切换,是一个重量级锁。
但是java6之后,对monitor对象的实现做了一些优化,有一个锁升级的概念:无锁、偏向锁、轻量级锁和重量级锁。

锁升级的过程:
偏向锁原理和升级过程
当线程1访问代码块并获取锁对象时,会在java对象头和栈帧中记录偏向的锁的threadID,因为偏向锁不会主动释放锁,因此以后线程1再次获取锁的时候,需要比较当前线程的threadID和Java对象头中的threadID是否一致,如果一致(还是线程1获取锁对象),则无需使用CAS来加锁、解锁;如果不一致(其他线程,如线程2要竞争锁对象,而偏向锁不会主动释放因此还是存储的线程1的threadID),那么需要查看Java对象头中记录的线程1是否存活,如果没有存活,那么锁对象被重置为无锁状态,其它线程(线程2)可以竞争将其设置为偏向锁;如果存活,那么立刻查找该线程(线程1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁,如果线程1 不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。

轻量级锁
为什么要引入轻量级锁?
轻量级锁考虑的是竞争锁对象的线程不多,而且线程持有锁的时间也不长的情景。因为阻塞线程需要CPU从用户态转到内核态,代价较大,如果刚刚阻塞不久这个锁就被释放了,那这个代价就有点得不偿失了,因此这个时候就干脆不阻塞这个线程,让它自旋这等待锁释放。

轻量级锁原理和升级过程
线程1获取轻量级锁时会先把锁对象的对象头MarkWord复制一份到线程1的栈帧中创建的用于存储锁记录的空间(称为DisplacedMarkWord),然后使用CAS把对象头中的内容替换为线程1存储的锁记录(DisplacedMarkWord)的地址;

如果在线程1复制对象头的同时(在线程1CAS之前),线程2也准备获取锁,复制了对象头到线程2的锁记录空间中,但是在线程2CAS的时候,发现线程1已经把对象头换了,线程2的CAS失败,那么线程2就尝试使用自旋锁来等待线程1释放锁。 自旋锁简单来说就是让线程2在循环中不断CAS

但是如果自旋的时间太长也不行,因为自旋是要消耗CPU的,因此自旋的次数是有限制的,比如10次或者100次,如果自旋次数到了线程1还没有释放锁,或者线程1还在执行,线程2还在自旋等待,这时又有一个线程3过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。

lock的实现readwritelock ReentrantLock实现原理:

基于AQS实现(队列同步器),什么是sqs呢?
aqs: 队列同步器,是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态(可见性),通过内置的FIFO队列来完成资源获取线程的排队工作,主要的使用方式是子类来继承它对方法做重写,既可以支持独占式地获取同步状态,也可以支持共享式地获取同步状态,实现的有ReentrantLock、ReentrantReadWriteLock和CountDownLatch等,
同步器依赖内部的同步队列(一个FIFO双向队列)来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成为一个节点 (Node)并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态,获取同步状态后可以执行。
Node节点的信息:当前线程、等待状态、前驱后驱节点等

ReentrantLock公平锁和非公平锁区别:
1、公平锁的新入线程不能直接获取锁,必须去排队(除非没有任何竞争发生)。而非公平锁新入的线程则可以先尝试获取锁,如果失败了再排队。
2、非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。

ReentrantLock的Condition实现原理:
ReentrantLock中同步队列只有一个,但是条件队列可能会有多个,可以有多个条件。
Condition有condition.await();condition.signal();condition.signalAll();方法,与obj的wait等方法对应,await方法把当前线程释放资源并放入等待队列中,调用signal方法会把当前线程cas的放入同步队列中去获取锁资源。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值