java并发编程与Synchronized简介
提到java并发编程我们就不得不谈到synchronized,我们现在学习synchronizd的底层原理。掌握以下知识点:
- 1,synchronized的底层原理
- 2,synchronized锁与JVM的实现
- 3,synchronized锁升级顺序
- 4,synchronized锁的优劣和应用场景
synchronized
synchronized中文意思是”同步”,或”同步锁”。
synchronized的作用是保证在同一时刻,被修饰的代码块或方法只会有一个线程执行,以达到保证并发环境下的线程安全。synchronized的使用
1,synchronized的3种使用方式 - 修饰实例方法:作用于当前实例加锁
- 修饰静态方法:作用于当前类对象加锁
- 修饰代码块:指定加锁对象,对给定对象加锁
2,synchronized代码示范1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21//实例方法
public synchronized void method() {
// 代码
}
//静态方法
public static synchronized void method() {
// 代码
}
//实例对象和类对象
synchronized (this) {
// 代码
}
synchronized (TestSyn.Class) /*类对象*/{
// 代码
}
synchronized的底层实现
synchronized的底层实现是完全依赖于java虚拟机的。
所以先看看JVM内存的存储:java对象头,以及Monitor对象。
1,java对象头
在JVM中,对象在内存中的存储布局,可以分为三个区域:
- 对象头(Header)
- 实例数据(INstance Data)
- 对齐填充(Padding)
Jvva对象头主要包含两部分数据: - 类型指针(Mark Word):是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
- 标记字段:用于存储对象自身的运行时数据,如哈希码、GC分代年龄、线程持有的锁、偏向线程ID、偏向时间戳等等。它是实现轻量级锁和偏向锁的关键
2,java对象存储位置
很明显synchronized使用的锁对象存储在java对象头里的标记字段里。
3,Monitor
synchronized的对象锁,指针指向一个monitor对象(由C++实现)的起始地址,每个对象实例都会有一个monitor。
Monitor描述为对象监视器,可以类比为一个特殊的房间,这个房间里有一些被保护的数据,Monitor保证每次只能有一个线程进入这个房间进行访问被保护的数据,进入房间即为持有Monitor,退出房间即为释放Monitor。
使用synchronized加锁的同步代码块在字节码引擎中执行时,主要就是通过锁对象的monitor的取用与释放来实现的。
4,线程状态流转在Monitor上的实现
描述为对象监视器,当多个线程同时请求某个对象监视器时,对象监视器会设置几种状态用来区分请求的线程:
- Contention:所有请求锁的线程将被首先放置到该竞争队列
- Entry List:Contention List中的那些有资格成为候选人的线程被移到Entry List
- Wait List:那些调用wait方法被阻塞的线程被放置到wait list。
- OnDeck:任何时刻最多只能有一个线程正在竞争锁,该线程被称为OnDeck
- owner:获得锁的线程称为owner
- !Owner:释放锁的线程
synchronized锁的升级顺序
锁解决了数据的安全性问题,但是同样带来的性能的下降,hotspot虚拟机的作者经过调查发现:大部分情况下,加锁的代码不仅仅存在多线程竞争,而且总是由一个线程多次获得。所以基于这样一个概率,在JDK 6之后进行了一些优化,为了减少获得锁和释放锁带来的性能开销,引入了偏向锁、轻量级锁、自旋锁、重量级锁,锁的状态根据竞争激烈的程度从低到高不断升级。
1,偏向锁
偏向锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其它的线程获取,则持有偏向锁的线程将永远不需要同步。
2,轻量级锁
如果明显存在其它线程申请锁,那么偏向锁将很快升级为轻量级锁。
3,自旋锁
自旋锁原理是如果持有锁的线程能在很短的时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等所持有锁的线程释放锁后立即获取锁,这样就避免用户线程和内核切换的消耗。
4,重量级锁
这就是原始的synchronized的实现,重量级锁的特点:其它线程试图获得锁的时候,都会被阻塞,只有持有锁的线程释放锁之后才会唤醒这些线程。