零 简单用法
同步关键字,用两种用法,一种是加在方法签名上,一种是里面包着一个monitor对象。
public class SyncDemo {
private static int[] array = {0};
private static synchronized void change(){
array[0]++;
}
/**
*
*/
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
new Thread(SyncDemo::change).start();
}
Thread.currentThread().join(1000);
System.out.println(array[0]);
}
}
一 wait与notify
wait方法与notify是对应的。wait方法定义在Object类中,当对象成为monitor时可以调用。Wait,具体含义就是等待别人notify。我讲一下细节Synchronized关键字后面有个括号,里面放个对象,这个对象就是monitor。如果没有加括号,那么静态方法上class对象就是monitor,非静态方法,this就是monitor。
默认是这样的,N个线程同步一个monitor对象。第一个线程拿到了monitor,其他线程只能等待,那么何时能主动调用wait呢?
就是拿到了monitor,这时候进入synchronized方法时,可以调用monitor对象的wait方法,也就是主动放弃辛苦拿到的monitor。而且wait方法只能由monitor去调用,否则会抛java.lang.IllegalMonitorStateException异常。
那么我们写个demo。AB两个线程,先让B运行,B运行到一半时,让A运行。A运行结束了,再运行B。那么我们需要三个对象:线程A、线程B和monitor。线程A,很简单,如果拿到monitor就调用monitor的wait让出去,然后再执行自己的方法。执行完了之后调用notify。线程B,拿到monitor之后,执行自己的任务,执行到一半,再notify,然后继续wait。
流程如下
A等待B运行 -> b 通知A -> b等待 -> A运行 -> A通知B -> B继续运行。
A 的代码:
public class ThreadA extends Thread {
private Object monitor;
public ThreadA(Object monitor) {
this.monitor = monitor;
}
@Override
public void run() {
synchronized (monitor) {
try {
monitor.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("A is running");
monitor.notify();
}
}
}
B的代码
public class ThreadB extends Thread {
private Object monitor;
public ThreadB(Object monitor) {
this.monitor = monitor;
}
@Override
public void run() {
synchronized (monitor) {
for (int i = 1; i <= 100; i++) {
try {
System.out.print("\rB is running. " + i + " %.");
if (i == 50) {
System.out.println();
monitor.notify();
monitor.wait();
}
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
测试类
public static void main(String[] args) {
Object lock = new Object();
new ThreadA(lock).start();
new ThreadB(lock).start();
}
运行结果
B is running. 50 %.
A is running
B is running. 100 %.
源码网址:https://e.coding.net/buildt/learn/thread.git
二 wait与interrupt
除notify外,interrupt也可以中断wait的状态,但是会引发InterruptedException异常。所以需要在wait代码中catch这个异常。我觉得interrupt适用于获取不到monitor的情况。此外,java的debug框架,比如说IDEA的debug,可以手动中断waiting中的线程,如下图所示:
需要注意的是,catch代码块需要重复中断一次以维护线程的中断标志位。如以下代码:
public class WaitingThead implements Runnable {
private Object lock = new Object();
@Override
public void run() {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
System.out.println("被中断");
Thread.currentThread().interrupt();
}
}
}
}
我写了个简单的main函数中断这个程序。可以通过控制台输入1或者debug启动鼠标点击线程来中断这个waiting的线程。
public class WaitInterruptDemo {
public static void main(String[] args) throws IOException {
final Thread thead = new Thread(new WaitingThead());
thead.start();
final int read = System.in.read();
if (read == '1') {
if (!thead.isInterrupted()) {
System.out.println((char) read);
thead.interrupt();
}
}
System.out.println(thead.isInterrupted());
}
}
三 线程的状态
由上面的例子,我们可以研究线程的状态。线程总共6种状态,在线程类的枚举java.lang.Thread.State中。总共有以下六种状态:NEW、RUNNABLE、BLOCKED、 WAITING、TIMED_WAITING、TERMINATED。
New是线程start之前的状态。
Runnable是线程start之后的状态。
Terminated是线程结束之后的状态
如果遇到monitor,这些状态就复杂了,于是乎就多了三种状态。
两个线程同步同一个Monitor之后,其中一个线程获取锁,进入运行中之后,另一个线程进入BLOCKED状态。
只有当线程调用了WAIT之后才会进入WAITING状态。
线程调用了sleep,或者调用了monitor的带时间参数的wait方法之后就进入了TIMED_WAITING状态。
Blocked与Waiting的相同点是,都处于不能运行的状态。区别在于,当monitor被释放后。Blocked状态的线程会马上进入运行状态,而waiting状态的线程继续waiting。
只有被notify之后,waiting状态的线程才会变成runnable。
四 synchronized实现原理
网上的说法是三级结构,偏向锁->轻量级锁->重量级锁。一步一步锁升级,一步一步锁膨胀。首先必须得看看源码,源码路径为:hotspot/src/share/vm/runtime/synchronizer.hpp。
源码里定义了三种进入方法:
static void fast_enter (Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS);
static void fast_exit (oop obj, BasicLock* lock, Thread* THREAD);
static void slow_enter (Handle obj, BasicLock* lock, TRAPS);
static void slow_exit (oop obj, BasicLock* lock, Thread* THREAD);
static void jni_enter (Handle obj, TRAPS);
static void jni_exit (oop obj, Thread* THREAD);
看快速进入方法,这里首先判断是否只有一个线程,如果是则直接返回,否则使用慢速进入方法,也就是我们说的轻量级锁。
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
if (UseBiasedLocking) {
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) ;
}
再看看轻量级锁代码,注意mark->is_neutral(),这个判断是否为“中性”,就是轻量级锁的条件,而这个的判断是读取对象头判断的。如果不满足这个条件,就进入重量级锁。也就是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.
lock->set_displaced_header(mark);
if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), 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.
lock->set_displaced_header(markOopDesc::unused_mark());
ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}
// This routine is used to handle interpreter/compiler slow case
// We don't need to use fast path here, because it must have
// failed in the interpreter/compiler code. Simply use the heavy
// weight monitor should be ok, unless someone find otherwise.
void ObjectSynchronizer::slow_exit(oop object, BasicLock* lock, TRAPS) {
fast_exit (object, lock, THREAD) ;
}
而JNI,也就是编译后的锁进入方法,也是在JIT编译器编译后的代码执行的逻辑。从代码可以看出,直接膨胀为重量级锁。
void ObjectSynchronizer::jni_enter(Handle obj, TRAPS) { // possible entry from jni enter
// the current locking is from JNI instead of Java code
TEVENT (jni_enter) ;
if (UseBiasedLocking) {
BiasedLocking::revoke_and_rebias(obj, false, THREAD);
assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
}
THREAD->set_current_pending_monitor_is_from_java(false);
ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
THREAD->set_current_pending_monitor_is_from_java(true);
}
所以总结一下,分为三种模式,JIT编译器编译为本地多了个锁消除优化,也就是无锁状态,连偏向锁都不需要了。
模式 | 锁升级过程 |
---|---|
解释器 | 偏向锁->轻量级锁->重量级锁 |
编译器 | 偏向锁或锁消除->轻量级锁->重量级锁 |
JNI | 偏向锁->重量级锁 |
五 偏向锁升级过程
首先看解释器的场景。解释器先会尝试,如果能获取偏向锁,也就是发现没有竞争,就直接执行方法了,也就是不执行虚拟机monitorenter指令,解释器代码在hotspot/src/share/vm/interpreter/bytecodeInterpreter.cpp的BytecodeInterpreter::run(interpreterState istate) 方法中的case method_entry代码块。执行monitorenter指令时,就会调用fast_enter还会再尝试一次调用偏向锁。如果这时候还未成功,才会变成轻量级锁,这就是revoke(再调用)的re(再)的由来。解释器部分代码如下:
if (!success) {
markOop displaced = rcvr->mark()->set_unlocked();
mon->lock()->set_displaced_header(displaced);
if (Atomic::cmpxchg_ptr(mon, rcvr->mark_addr(), displaced) != displaced) {
// Is it simple recursive case?
if (THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {
mon->lock()->set_displaced_header(NULL);
} else {
CALL_VM(InterpreterRuntime::monitorenter(THREAD, mon), handle_exception);
}
}
}
再看看编译器的场景。源码位于hotspot/src/share/vm/c1/c1_Runtime1.cpp。但我找到的源码是编译器模式monitorenter实现,内部直接就调用了fast_enter方法。但是我不确定编译器模式有没有做像解释器那样连monifterenter指令都不调用的偏向锁优化。
JRT_ENTRY_NO_ASYNC(void, Runtime1::monitorenter(JavaThread* thread, oopDesc* obj, BasicObjectLock* lock))
NOT_PRODUCT(_monitorenter_slowcase_cnt++;)
if (PrintBiasedLockingStatistics) {
Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
}
Handle h_obj(thread, obj);
assert(h_obj()->is_oop(), "must be NULL or an object");
if (UseBiasedLocking) {
// Retry fast entry if bias is revoked to avoid unnecessary inflation
ObjectSynchronizer::fast_enter(h_obj, lock->lock(), true, CHECK);
} else {
if (UseFastLocking) {
// When using fast locking, the compiled code has already tried the fast case
assert(obj == lock->obj(), "must match");
ObjectSynchronizer::slow_enter(h_obj, lock->lock(), THREAD);
} else {
lock->set_obj(obj);
ObjectSynchronizer::fast_enter(h_obj, lock->lock(), false, THREAD);
}
}
JRT_END
偏向锁的升级代码在hotspot/src/share/vm/runtime/biasedLocking.cpp文件的revoke_bias函数中。这段代码有点长啊。这段就是网上常说的,如果线程1存活,线程2竞争不到就升级为轻量级锁。注释我翻译一下:
偏向线程存活。继续检查它是否拥有锁,如果拥有,那就将头写入线程的栈中,否则,将对象头恢复到无锁或无偏向状态。
// Thread owning bias is alive.
// Check to see whether it currently owns the lock and, if so,
// write down the needed displaced headers to the thread's stack.
// Otherwise, restore the object's header either to the unlocked
// or unbiased state.
GrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info(biased_thread);
BasicLock* highest_lock = NULL;
for (int i = 0; i < cached_monitor_info->length(); i++) {
MonitorInfo* mon_info = cached_monitor_info->at(i);
if (mon_info->owner() == obj) {
if (TraceBiasedLocking && Verbose) {
tty->print_cr(" mon_info->owner (" PTR_FORMAT ") == obj (" PTR_FORMAT ")",
(void *) mon_info->owner(),
(void *) obj);
}
// Assume recursive case and fix up highest lock later
markOop mark = markOopDesc::encode((BasicLock*) NULL);
highest_lock = mon_info->lock();
highest_lock->set_displaced_header(mark);
} else {
if (TraceBiasedLocking && Verbose) {
tty->print_cr(" mon_info->owner (" PTR_FORMAT ") != obj (" PTR_FORMAT ")",
(void *) mon_info->owner(),
(void *) obj);
}
}
}
if (highest_lock != NULL) {
// Fix up highest lock to contain displaced header and point
// object at it
highest_lock->set_displaced_header(unbiased_prototype);
// Reset object header to point to displaced mark
obj->set_mark(markOopDesc::encode(highest_lock));
assert(!obj->mark()->has_bias_pattern(), "illegal mark state: stack lock used bias bit");
if (TraceBiasedLocking && (Verbose || !is_bulk)) {
tty->print_cr(" Revoked bias of currently-locked object");
}
} else {
if (TraceBiasedLocking && (Verbose || !is_bulk)) {
tty->print_cr(" Revoked bias of currently-unlocked object");
}
if (allow_rebias) {
obj->set_mark(biased_prototype);
} else {
// Store the unlocked value into the object's header.
obj->set_mark(unbiased_prototype);
}
}
return BiasedLocking::BIAS_REVOKED;
这个操作还是在fast_enter里判断了返回值,因为这种场景返回值时BIAS_REVOKED,这个枚举不包含偏向,所以进入slow_enter,也就是轻量级锁代码。
五 重量级锁
重量级锁的方法,不在这这个cpp文件里定义。文件为hotspot/src/share/vm/runtime/objectMonitor.cpp。重量级锁,字段很多,但是最重要的是三个集合:_cxq,_EntryList和_WaitSet。cxq的定义是最近到达线程,在竞争时,线程先加入到cxq。后序再从cxq迁移到EntryList。而WaitSet是调用了wait方法的线程集合。结构上cxq是单链表,而EntryList是双链表。那程序员要关注的重点是什么?毕竟重量级锁这么复杂。
首先inflate方法,只是把轻量级锁转为重量级锁。
// Inflate light weight monitor to heavy weight monitor
static ObjectMonitor* inflate(Thread * Self, oop obj);
在重量级锁获取不到时,会调用调用Self->_ParkEvent->park()函数进入BLOCKED状态,以下代码位于void ATTR ObjectMonitor::EnterI (TRAPS)函数,源码在hotspot/src/share/vm/runtime/objectMonitor.cpp中:
for (;;) {
if (TryLock (Self) > 0) break ;
assert (_owner != Self, "invariant") ;
if ((SyncFlags & 2) && _Responsible == NULL) {
Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
}
// park self
if (_Responsible == Self || (SyncFlags & 1)) {
TEVENT (Inflated enter - park TIMED) ;
Self->_ParkEvent->park ((jlong) RecheckInterval) ;
// Increase the RecheckInterval, but clamp the value.
RecheckInterval *= 8 ;
if (RecheckInterval > 1000) RecheckInterval = 1000 ;
} else {
TEVENT (Inflated enter - park UNTIMED) ;
Self->_ParkEvent->park() ;
}
if (TryLock(Self) > 0) break ;
// The lock is still contested.
// Keep a tally of the # of futile wakeups.
// Note that the counter is not protected by a lock or updated by atomics.
// That is by design - we trade "lossy" counters which are exposed to
// races during updates for a lower probe effect.
TEVENT (Inflated enter - Futile wakeup) ;
if (ObjectMonitor::_sync_FutileWakeups != NULL) {
ObjectMonitor::_sync_FutileWakeups->inc() ;
}
++ nWakeups ;
// Assuming this is not a spurious wakeup we'll normally find _succ == Self.
// We can defer clearing _succ until after the spin completes
// TrySpin() must tolerate being called with _succ == Self.
// Try yet another round of adaptive spinning.
if ((Knob_SpinAfterFutile & 1) && TrySpin (Self) > 0) break ;
// We can find that we were unpark()ed and redesignated _succ while
// we were spinning. That's harmless. If we iterate and call park(),
// park() will consume the event and return immediately and we'll
// just spin again. This pattern can repeat, leaving _succ to simply
// spin on a CPU. Enable Knob_ResetEvent to clear pending unparks().
// Alternately, we can sample fired() here, and if set, forgo spinning
// in the next iteration.
if ((Knob_ResetEvent & 1) && Self->_ParkEvent->fired()) {
Self->_ParkEvent->reset() ;
OrderAccess::fence() ;
}
if (_succ == Self) _succ = NULL ;
// Invariant: after clearing _succ a thread *must* retry _owner before parking.
OrderAccess::fence() ;
}
而park的实现各个操作系统不一样,以Windows为例子,调用了Windows系统独有的方法WaitForSingleObject,源代码处于hotspot/src/os/windows/vm/os_windows.cpp中:
void os::PlatformEvent::park () {
guarantee (_ParkHandle != NULL, "Invariant") ;
// Invariant: Only the thread associated with the Event/PlatformEvent
// may call park().
int v ;
for (;;) {
v = _Event ;
if (Atomic::cmpxchg (v-1, &_Event, v) == v) break ;
}
guarantee ((v == 0) || (v == 1), "invariant") ;
if (v != 0) return ;
// Do this the hard way by blocking ...
// TODO: consider a brief spin here, gated on the success of recent
// spin attempts by this thread.
while (_Event < 0) {
DWORD rv = ::WaitForSingleObject (_ParkHandle, INFINITE) ;
assert (rv == WAIT_OBJECT_0, "WaitForSingleObject failed") ;
}
// Usually we'll find _Event == 0 at this point, but as
// an optional optimization we clear it, just in case can
// multiple unpark() operations drove _Event up to 1.
_Event = 0 ;
OrderAccess::fence() ;
guarantee (_Event >= 0, "invariant") ;
}