Synchronized,ReentrantLock的实现原理及异同比较 、Java中其他锁的理解

1)Synchronized 同步锁


作用域:不同加锁方式对应不同作用域

1) 对象锁

当一个对象访问Synchronized方法时,锁类型为对象锁。
此时该对象不能同时访问类中其他Synchronized方法
。若一个对象中有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,则其它线程不能同时访问这个对象中任何一个synchronized方法。
此外,不同对象实例的synchronized方法是不相干预的。也就是说,其它线程可以同时访问此类下的另一个对象实例中的synchronized方法;

2) 类锁 :对该类的所有对象实例起作用。

当对静态方法加锁时,锁类型为类锁。相当于给类加锁(synchronized.Class),静态方法被调用时(类名.方法名),会先检测调用类有没有持有锁,持有锁时,等待。

锁的底层实现:

使用一个monitor对象,由ObjectMonitor实现(C++代码)
Synchronized,ReentrantLock的实现原理及异同比较 、Java中其他锁的理解_第1张图片

Class字节码中
1)对象加锁时,使用monitorenter指令得到monitor对象。检查owner是否为空
Synchronized,ReentrantLock的实现原理及异同比较 、Java中其他锁的理解_第2张图片
monitorenter操作的目标一定是对象,类型是reference。Reference实际就是堆里的一个存放对象的地址。每个对象(reference)都有一个monitor对应。
2)方法加锁时
常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。
Synchronized,ReentrantLock的实现原理及异同比较 、Java中其他锁的理解_第3张图片

注:

1)类锁与对象锁互不干扰。
	 个人推测原因:
	 静态方法:与静态成员变量一样,属于类的本身,在类装载的时候被装载到内存,
	 非静态方法:又叫实例化方法,属于实例对象,实例化后才会分配内存,必须通过类的实例来引用。
	 不同地址,所以访问互不干扰

2)wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,
  否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

3)Synchronized缺点:当一个线程持有锁时,不能立即返回,也不能被打断。其他线程只能阻塞。

2)ReentrantLock


ReentrantLock :核心为AQS(AbstractQueueSynchronzer)。AQS基于FIFO双向队列实现。
队列的基本元素是Node,AQS为每个线程创建一个Node进行调度

ReentrantLock根据构造方法中传入的boolean值来创建一个静态抽象内部类Sync的子类,即公平锁或非公平锁
公平锁与非公平锁的比较
不公平锁:可能会产生饥饿现象。效率较高
公平锁:不会产生饥饿现象,效率较低
Synchronized,ReentrantLock的实现原理及异同比较 、Java中其他锁的理解_第4张图片

ReentrantLock内部类结构

Synchronized,ReentrantLock的实现原理及异同比较 、Java中其他锁的理解_第5张图片
Synchronized,ReentrantLock的实现原理及异同比较 、Java中其他锁的理解_第6张图片
Synchronized,ReentrantLock的实现原理及异同比较 、Java中其他锁的理解_第7张图片Synchronized,ReentrantLock的实现原理及异同比较 、Java中其他锁的理解_第8张图片

线程调用lock()方法获取锁的过程

1)	使用CAS操作尝试修改当前同步状态。
	a)	状态为0时,可直接修改AQS状态为1,成功得到锁
		i.	将AQS的父类中exclusiveOwnerThread 设为当前线程(表示当前线程为独占拥有线程)
	b)	状态不为0时,判断当前线程是否和执行线程Owner一致。
		i.	一致时,state+1(锁的重入)
		ii.	不一致时,调用addWaiter(Node.EXCLUSIVE)方法。为线程创建node节点
		iii.当队列为空时,调用enq方法。方法内部自旋,首先判断尾是否为空
			1.为空时,创建一个空节点。赋head = tail
			2.方法内自旋,此时尾不为空,将节点插入尾部
		iv.	调用方法,设置Node中waitState值为-1,标志该Node中线程被取消。
		v.	调用parkAndCheckInterrupt()方法,方法内部park(this),将线程状态由Runnable,修改为waiting

线程调用unlock()方法释放锁的过程

先调用release()方法。
	1)	方法内部调用tryrelease()方法
		a.	tryrelease():对state-1操作。此时若state为0,返回true。否则返回false.
	2)	当返回true时,进入if方法体,首先判断队列的head != null 且head.waitStatus != 0(当前实际值为-1),则调用unparkSuccessor(Node)方法 
		a.	unparkSuccessor(Node)方法内部。
			i.	修改waitState状态为0,
			ii.	调用unpark(thread)方法,使线程由Waiting变为Runnable

3)Synchronized与ReentrantLock的异同

同:
	1)都为可重入锁:同一个线程每进入一次,锁的计数器都自增1,要等到锁的计数器下降为0时才能释放锁
	2)优化思想:都试图在用户态就把加锁问题解决,避免进入内核态的线程阻塞。
	
异:
	1)Synchronized是依赖于JVM实现的(部分为C++代码),而ReenTrantLock是JDK实现的(Java代码实现)
	2)ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。
 	3)ReenTrantLock提供了一个Condition(条件)类,可实现分组唤醒线程,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
	4)ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。synchronized不可中断

4)对象实例结构 Java中各种锁的理解


对象实例结构

分为三部分:对象头、实例数据、对齐填充
Header(对象头):
	1)存放自身运行时数据: 哈希值、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳
	2)类型指针:虚拟机通过该指针确定对象是哪个类的实例
InstanceData(实例数据):对象存储的有效信息,即字段内容
Padding(对齐填充):占位符。Java要求对象大小为8字节的整数倍,当数据没有对齐时,使用对齐填充

Synchronized,ReentrantLock的实现原理及异同比较 、Java中其他锁的理解_第9张图片

公平锁与非公平锁

公平锁:多个线程等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁
非公平锁:在锁释放时,随机指定一个线程得到锁。
Synchronized为非公平锁,
ReentrantLock默认为非公平锁,但可通过带Boolean值的构造函数来指定使用公平锁

自旋锁与自适应自旋

背景:
1.许多应用中,共享数据的锁定常常为短暂持有。
2.挂起线程和恢复线程的操作都需要转入内核态中完成。系统高并发时,大量挂起和恢复十分影响性能。
3.当一个机器中有多个处理器,可以使多个线程并发执行时,我们可以让后一个请求锁的线程。执行一个短暂自循环(继续占用处理器),看看持有锁的线程是否会很快释放锁。

自旋锁:短暂占用处理器,节省挂起和恢复线程的资源。
自适应自旋锁:自旋的时间不再固定,而是根据持有相同锁的前一个线程的自旋时间和锁的状态来决定。

轻量级锁:

目的:在没有的多线程的竞争下,减少 重量级锁使用操作系统中互斥量的开销
实现:使用CAS操作修改对象头中标志位。

00表示持有锁
01表示未锁定。
10表示膨胀为重量级锁
当两个以上线程竞争时,轻量级锁不再有效,会膨胀为重量级锁

优点:轻量级锁使用CAS操作标志位,避免了重量级锁使用互斥量的开销
缺点:当存在锁竞争时,除了互斥量开销外,还有CAS操作,因此在存在竞争的情况下,轻量级锁比重量级锁更慢

偏向锁:

目的:消除数据在无竞争情况下的同步原语,进一步提升程序运行性能。
实现:对于持有偏向锁的线程,在接下来程序的执行过程中,如果该锁没有被其他线程获取,则永远不进行同步操作。

轻量级锁在无竞争情况下,使用CAS操作。
偏向锁在无竞争情况下,直接消除同步,不做任何操作。

当程序总是存在多线程竞争的情况下,可通过禁止偏向锁优化以提升性能
使用参数-XX:-UseBiaseLocking

reentrantlock :已经实现偏向锁。
1.在加锁时,会判断是否为当前持锁线程(使用TreadID 或 Thread本身来进行判断)
2.是当前持锁线程时,不进行CAS操作,直接对count+1。减少了一次CAS操作锁带来的性能开销。

你可能感兴趣的:(多线程)