1. Synchronized

零 简单用法

  同步关键字,用两种用法,一种是加在方法签名上,一种是里面包着一个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") ;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

醒过来摸鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值