锁是应用开发中的一种常见同步机制, 而synchronized则是java中的一种内建的同步方式, 所以也有人称其为Intrinsic Locking, 它提供了互斥的语义和可见性保证, 当一个线程已经获取当前对象锁时, 其他试图获取同一个锁的线程只能等待或者阻塞
一. 首先我们需要理解什么是线程安全
在拥有共享数据的多个线程并行执行的程序中,线程安全需要通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。即需要保证多线程环境下共享的, 可修改的状态[数据]的正确性;
反向来讲, 如果状态不是共享, 或者不可修改的, 也就不存在线程安全问题, 进而可以推理出保证线程安全的两个办法:
1) 封装: 通过封装, 我们将对象内部状态隐藏, 保护起来, 对外提供统一的多线程修改正确性表现;
2) 不可变: 设置状态初始化后就不能再改变, 比如声明final和immutable;
二. 线程安全需要保证的几个特性
- 原子性: 简单来说就是一系列操作在执行过程中不会被其他线程干扰, 对资源的访问一般表现为独占, 这一点一般通过同步机制实现;
- 可见性: 指一个线程修改了某个共享变量, 其状态能够立即被其他线程知晓, java共享内存模型下, 通常就是指线程本地状态立刻反映到主内存上, 这样就能被其他线程感知, volatile就有保证可见性的功能;
- 有序性: 保证线程内串行执行的语义, 避免指令重排序, 因为指令重排可能会导致最终结果非预料的;
在Java 5以前, synchronized是仅有的同步手段, 在代码中synchronized可以用来修饰方法, 代码块, 本质上都是将锁加到某个对象上面;
三. 用代码来看synchronized的功能作用
package org.hinsteny.jvm.synchronize;
/**
* @author Hinsteny
* @version $ID: ThreadSafeSample 2018-06-30 10:57 All rights reserved.$
*/
public class ThreadSafeSample {
private int sharedState;
public static void main(String[] args) throws InterruptedException {
testNonSafeAction();
testSafeAction();
}
private static void testNonSafeAction() throws InterruptedException {
ThreadSafeSample sample = new ThreadSafeSample();
Thread a = new Thread(() -> {
sample.nonSafeAction();
});
Thread b = new Thread(() -> {
sample.nonSafeAction();
});
a.start();
b.start();
a.join();
b.join();
}
private static void testSafeAction() throws InterruptedException {
ThreadSafeSample sample = new ThreadSafeSample();
Thread a = new Thread(() -> {
sample.safeAction();
});
Thread b = new Thread(() -> {
sample.safeAction();
});
a.start();
b.start();
a.join();
b.join();
}
private void nonSafeAction() {
while (sharedState < 100000) {
int former = sharedState++;
int latter = sharedState;
if (former != latter - 1) {
System.err.println(String.format("Observed data state, former is %s, latter is %s", former, latter));
break;
}
}
System.err.println(String.format("Observed data state thread [%s] is finished", Thread.currentThread().getName()));
}
private void safeAction() {
while (sharedState < 100000) {
synchronized (this) {
int former = sharedState++;
int latter = sharedState;
if (former != latter - 1) {
System.err.println(String.format("Observed data state, former is %s, latter is %s", former, latter));
}
}
}
System.err.println(String.format("Observed data state thread [%s] is finished", Thread.currentThread().getName()));
}
}
运行结果为:
Observed data state, former is 6804, latter is 6916
Observed data state thread [Thread-1] is finished
Observed data state thread [Thread-0] is finished
Observed data state thread [Thread-2] is finished
Observed data state thread [Thread-3] is finished
反编译synchronized语句块得到:
private void safeAction();
Flags: PRIVATE
Code:
linenumber 59
0: aload_0 /* this */
1: getfield org/hinsteny/jvm/synchronize/ThreadSafeSample.sharedState:I
4: ldc 100000
6: if_icmpge 81
linenumber 60
9: aload_0 /* this */
10: dup
11: astore_1
12: monitorenter
linenumber 61
13: aload_0 /* this */
14: dup
15: getfield org/hinsteny/jvm/synchronize/ThreadSafeSample.sharedState:I
18: dup_x1
19: iconst_1
20: iadd
21: putfield org/hinsteny/jvm/synchronize/ThreadSafeSample.sharedState:I
24: istore_2 /* former */
linenumber 62
25: aload_0 /* this */
26: getfield org/hinsteny/jvm/synchronize/ThreadSafeSample.sharedState:I
29: istore_3
linenumber 63
30: iload_2 /* former */
31: iload_3
32: iconst_1
33: isub
34: if_icmpeq 66
linenumber 64
37: getstatic java/lang/System.err:Ljava/io/PrintStream;
40: ldc "Observed data state, former is %s, latter is %s"
42: iconst_2
43: anewarray Ljava/lang/Object;
46: dup
47: iconst_0
48: iload_2 /* former */
49: invokestatic java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
52: aastore
53: dup
54: iconst_1
55: iload_3
56: invokestatic java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
59: aastore
60: invokestatic java/lang/String.format:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
63: invokevirtual java/io/PrintStream.println:(Ljava/lang/String;)V
linenumber 66
66: aload_1
67: monitorexit
68: goto 78
71: astore 4
73: aload_1
74: monitorexit
75: aload 4
77: athrow
78: goto 0
linenumber 68
81: getstatic java/lang/System.err:Ljava/io/PrintStream;
84: ldc "Observed data state thread [%s] is finished"
86: iconst_1
87: anewarray Ljava/lang/Object;
90: dup
91: iconst_0
92: invokestatic java/lang/Thread.currentThread:()Ljava/lang/Thread;
95: invokevirtual java/lang/Thread.getName:()Ljava/lang/String;
98: aastore
99: invokestatic java/lang/String.format:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
102: invokevirtual java/io/PrintStream.println:(Ljava/lang/String;)V
linenumber 69
105: return
Exceptions:
Try Handler
Start End Start End Type
----- ----- ----- ----- ----
13 68 71 78 Any
71 75 71 78 Any
可以看到synchronized底层使用monitorenter/monitorexit实现了同步的语义
结论: 多线程访问共享变量时, 很容易发生互相干扰, 产生预期之外的结果;
四. Monitor 对象是同步的基本实现单元
在java 6 之前, Monitor 的实现完全是依靠操作系统内部的互斥锁, 因此需要进行用户态到内核态的切换, 所以同步操作是一个无差别的重量级操作;
现代的(Oracle) JDK中, JVM对此进行了大刀阔斧地改进, 提供了三种不同的Monitor实现, 也就是常说的三种不同的锁: 偏向锁(Biased Locking), 轻量级锁和重量级锁, 大大的改进了其性能.
而所谓锁的升级, 降级, 就是JVM优化synchronized运行的机制, 当JVM检测到不同的竞争情况时, 会自动切换到适合的锁实现, 这种切换就是锁的升级, 降级.
- 当没有竞争出现时, 会默认使用偏向锁, JVM 会利用CAS操作, 在对象头上的Mark Word部分设置线程ID, 以表示这个对象偏向于当前线程, 所以不涉及真正的互斥锁. 这样的假设是基于在很多应用场景中, 大部分对象生命周期中最多会被一个线程锁定, 使用偏向锁可以减低无竞争开销;
- 如果有其他线程试图锁定某个已经被偏向锁过的对象, JVM 就需要撤销(revoke) 偏向锁, 并切换到轻量级锁实现.
- 轻量级锁依赖CAS操作Mark Word来试图获取锁, 如果重试成功, 就是用普通的轻量级锁; 否则, 进一步升级为重量级锁.
- 当JVM进入安全点的时候, 会检查是否有闲置的Monitor, 然后试图进行锁降级;
五. 从源码层面, 看看synchronized的底层实现
查看最新的JVM源码地址: hotspot
从java对象头描述文件中看对象头结构 markOop
1 . sharedRuntime.cpp/hpp: 解释器和编译器运行时的基类. 下面的代码提现了synchronized的主要执行逻辑;
// Handles the uncommon case in locking, i.e., contention or an inflated lock.
JRT_BLOCK_ENTRY(void, SharedRuntime::complete_monitor_locking_C(oopDesc* _obj, BasicLock* lock, JavaThread* thread))
if (!SafepointSynchronize::is_synchronizing()) {
// Only try quick_enter() if we're not trying to reach a safepoint
// so that the calling thread reaches the safepoint more quickly.
if (ObjectSynchronizer::quick_enter(_obj, thread, lock)) return;
}
// NO_ASYNC required because an async exception on the state transition destructor
// would leave you with the lock held and it would never be released.
// The normal monitorenter NullPointerException is thrown without acquiring a lock
// and the model is that an exception implies the method failed.
JRT_BLOCK_NO_ASYNC
oop obj(_obj);
if (PrintBiasedLockingStatistics) {
Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
}
Handle h_obj(THREAD, obj);
if (UseBiasedLocking) {
// Retry fast entry if bias is revoked to avoid unnecessary inflation
ObjectSynchronizer::fast_enter(h_obj, lock, true, CHECK);
} else {
ObjectSynchronizer::slow_enter(h_obj, lock, CHECK);
}
assert(!HAS_PENDING_EXCEPTION, "Should have no exception here");
JRT_BLOCK_END
JRT_END
2 . UseBiasedLocking: 一个基础设置, 在JVM启动时可以人为指定是否开启偏向锁; 其实偏向锁并不适合所有应用场景, 因为撤销操作(revoke)是比较重的行为, 只有当存在较多不会真正竞争的synchronized块时, 才能体现出明显改善; 因此实践中, 还是需要考虑具体业务场景, 并测试后, 再决定是否开启/关闭偏向锁, 关闭偏向锁的指令;
-XX:-UseBiasedLocking
3 . fast_enter: 就是完整的锁获取逻辑, 下面看一下 synchronizer 中的获取锁逻辑
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock,
bool attempt_rebias, TRAPS) {
// 1.先判断应用程序是否启动了偏向锁
if (UseBiasedLocking) {
// 2.检测当前是否处于安全点
if (!SafepointSynchronize::is_at_safepoint()) {
// 没有检测到安全点, 随即进行偏向锁的获取
BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
return;
}
} else {
assert(!attempt_rebias, "can not rebias toward VM thread");
// 检测到安全点, 进行相关处理
BiasedLocking::revoke_at_safepoint(obj);
}
assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
}
slow_enter(obj, lock, THREAD);
}
4 . 不能获取到偏向锁, 那再看看slow_enter的逻辑;
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
// 获取当前要被加锁对象的头
markOop mark = obj->mark();
assert(!mark->has_bias_pattern(), "should not see bias pattern here");
// 判断头是否处于中性锁状态
if (mark->is_neutral()) {
// Anticipate successful CAS -- the ST of the displaced mark must
// be visible <= the ST performed by the CAS.
// 将目前的mark_word复制到 Displaced Header上
lock->set_displaced_header(mark);
// 利用CAS设置对象的Mark Word
if (mark == obj()->cas_set_mark((markOop) lock, mark)) {
TEVENT(slow_enter: release stacklock);
return;
}
// Fall through to inflate() ...
}
// 检测当前是否存在锁竞争
else if (mark->has_locker() &&
THREAD->is_lock_owned((address)mark->locker())) {
assert(lock != mark->locker(), "must not re-lock the same lock");
assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock");
// 清楚信息
lock->set_displaced_header(NULL);
return;
}
// The object header will never be displaced to this lock,
// so it does not matter what the value is, except that it
// must be non-zero to avoid looking like a re-entrant lock,
// and must not look locked either.
// 重置 Displaced Header
lock->set_displaced_header(markOopDesc::unused_mark());
ObjectSynchronizer::inflate(THREAD,
obj(),
inflate_cause_monitor_enter)->enter(THREAD);
}
- 设置 Displaced Header,然后利用 cas_set_mark 设置对象 Mark Word,如果成功就成功获取轻量级锁。
- 否则 Displaced Header,然后进入锁膨胀阶段,具体实现在 inflate 方法中。
六. 关于synchronized的一些使用建议
虽然synchronized在使用层面简单易用, 同时新一代的JVM也对它的底层实现做了一些优化, 但是在实际业务场景中, 我们还是要看具体看锁的粒度, 竞争情况, 锁类型(读/写)等多个条件, 再决定使用哪种锁;
常见的锁:
synchronized: 在锁竞争情况比较少发生的情况下, 重发发挥偏向锁的优势, 效率则比较高;
ReentrantLock:
ReentrantReadWriteLock:
StampedLock: