Synchronized的实现原理
两个重要的概念:一个是对象头,另一个是monitor。
Java 虚拟机中的同步(Synchronization)基于进入和退出Monitor对象实现
monitorenter指令插入到同步代码块的开始位置,monitorexit指令插入到同步代码块的结束位置,
JVM需要保证每一个monitorenter都有一个monitorexit与之相对应。
任何对象都有一个monitor与之相关联,当且一个monitor被持有之后,他将处于锁定状态。
线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor所有权,即尝试获取对象的锁
ReenTrantLock实现的原理:
两个比较重要的概念
第一个:状态变量,用于存储当前锁的状态,0为未被占用,1为被占用,2,3,4等均为被占用,且被同一线程重入了多次
第二个:双线链表,用于存储排队获取锁的线程,只有队列中第一个线程被唤醒后,才有资格获取锁
(1)获取锁
通过CAS(乐观锁)去尝试修改状态变量的值(从0修改到1)
若修改成功,即获得锁,并执行锁内程序
若修改失败,将当前线程通过CAS加入上面锁的双向链表(等待队列)的尾部,并通过自旋,持续判断当前队列节点是否可以获取锁
(2)释放锁
1. 释放锁的线程将状态变量的值从1设置为0,并唤醒等待(锁)队列中的队首节点
2. 被唤醒的线程(队列中的队首节点)和未进入队列并且准备获取的线程竞争获取锁,被唤醒的线程若竞争到了锁,则则将其出列,将下一个节点变成队列的头结点
synchronized和lock的区别
(1)Lock是一个接口,而synchronized是Java中的关键字。(设计层次)
(2)Lock可以让等待锁的线程响应中断,线程可以中断去干别的事务,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去。(锁的获取)
(3)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生。
而Lock在发生异常时,不会自动是放占有的锁,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁。(锁的释放)
(4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。(锁状态)
(5)synchronized 可重入、不可中断、非公平;Lock 可重入、可中断、可公平可非公平(锁类型)
(6)Lock可以提高多个线程进行读操作的效率。(性能)
(7)ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程
有了Synchronized为什么需要Lock
线程进入同步代码块时,如果锁被其他线程占用,该线程只能等待
Lock提供了这三种方式来弥补Synchronized的不足,使得我们能写出更加安全,健壮的代码。
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterrptedException;
void lockInterruptibly() throws InterruptedException;
ReentrantLock获取锁的方式:
(1) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
(2)tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
(3)tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
(4)lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断
为什么需要读写锁
ReentrantLock是一个互斥锁,保证在同一时刻只有一个线程获取资源,无论是写写,读读,读写任何场景都是互斥的。
这种互斥锁在常见的高并发场景下会显现出糟糕的低吞吐量问题。
读写锁的作用
一个资源可以被多个读操作同时访问,或者被一个写操作独占。但是两者不能同时访问。
效率问题
当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。
Lock可以使得多个线程都只是进行读操作时,线程之间不会发生冲突
但是采用synchronized关键字来实现同步的话,当一个线程在进行读操作时,其他线程只能等待无法进行读操作。
ReadWriteLock也是一个接口,在它里面只定义了两个方法,readLock()和writeLock()
一个用来获取读锁,一个用来获取写锁。也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作
ReentrantReadWriteLock实现了ReadWriteLock接口
ReentrantReadWriteLock是Lock的另一种实现方式,我们已经知道了ReentrantLock是一个排他锁,同一时间只允许一个线程访问,
而ReentrantReadWriteLock允许多个读线程同时访问,但不允许写线程和读线程、写线程和写线程同时访问。相对于排他锁,提高了并发性。
在实际应用中,大部分情况下对共享数据(如缓存)的访问都是读操作远多于写操作,这时ReentrantReadWriteLock能够提供比排他锁更好的并发性和吞吐量。
Lock原理总结总结
lock的存储结构:一个int类型状态值(用于锁的状态变更),一个双向链表(用于存储等待中的线程)
lock获取锁的过程:本质上是通过CAS来获取状态值修改,如果当场没获取到,会将该线程放在线程等待链表中。
lock释放锁的过程:修改状态值,调整等待链表。