专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

初识 Synchronized

Synchronized

1、可用范围

    public void demo() {
        synchronized (synchronizedDemo.class) {

        }
    }

    public synchronized static void demo() {

    }

  • 方法
    public synchronized void demo() {

     }

    public void demo() {
            synchronized (this) {

            }
    }

2、Markwork锁信息存储

96_1.png

1、 为什么每个 java 对象都可以作为锁对象

> Java 中的对象,都是派生自 Object 类,每个 Java Object 在 JVM 源码中都有一个 native 对象与之对应

2、 synchronized (lock) ,关于锁的操作,都是对 lock 这个对象的生命周期进行操作
3、 Mark word,记录了对象和锁的相关信息

    enum { age_bits                   = 4, // 分带年龄
             lock_bits                = 2, // 锁标记位
             biased_lock_bits         = 1, // 偏向锁标记,标记是否为偏向锁
             max_hash_bits            = BitsPerWord - age_bits - lock_bits - biased_lock_bits,
             hash_bits                = max_hash_bits > 31 ? 31 : max_hash_bits,
             cms_bits                 = LP64_ONLY(1) NOT_LP64(0),
             epoch_bits               = 2 // 偏向锁的时间戳
      };

4、锁的升级

经过 3. 分析,发现在内核态和用户态之间来回切换,对性能有所影响,所以在 JDK 1.6 之后进行了更新优化

4.1 锁的升级步骤

偏向锁 -> 轻量级锁 -> 重量级锁

其中,偏向锁和轻量级锁,可以说是一种无锁状态,其中轻量级锁也可以说是自旋锁

4.2 偏向锁
4.2.1 大体理论

偏向锁的大体理论就是,当一个线程访问同步代码的时候,会在对象头里存储自己当前的线程ID,当要获取锁的时候和锁对象的 Mark word 信息进行对比,若一致则直接进入,执行同步,当后续这个线程在进入的时候不会进行加锁和解锁操作,若对比时不一致,代表此时有线程和它进行竞争,这时会对已经获取到偏向锁的线程进行偏向锁的撤销,并将其升级为轻量级锁

偏向锁一般只适用于一个线程访问的情况

4.2.1 使用前提
  • biased_lock_bits 偏向锁标记为 1
  • ThreadId 为空

    表示为可偏向状态,信息存储在 Mark word 中

4.2.3 执行流程
  • 获取对象的 Mark word 信息,确定当前为可偏向状态
  • 如果是可偏向状态,通过 CAS 操作,将线程ID等信息,写入到锁对象的 Mark word 中

    • 如果 CAS 写入成功,则当前锁对象的 Mark word 存储的 ThreadID 就是当前线程的 ThreaId ,可以执行同步代码块中的内容
    • 如果 CAS 写入失败,则表示当前锁对象已经有线程获取到了,也就是现在存在了锁竞争的情况,这时会将先前已经获取锁的对象的偏向锁进行撤销,并将其升级为轻量级锁(这些操作会等待到一个全局安全点,也就是没有执行字节码的时候进行操作)
  • 如果是已偏向状态,则直接对比 ThreadId 信息即可
    • 相等,直接进行执行操作
    • 不相等,则表明竞争,和上述相同的操作

96_2.png

4.2.4 设置关闭

-XX:-UseBiasedLocking=false 关闭

4.3 轻量级锁
4.3.1 大体理论

在偏向锁阶段出现竞争的话,会将锁信息撤销,并将 Mark Work 中的 ThreadID 信息置为空,将锁升级为轻量级锁,轻量级锁也可以说是自旋锁,它适用于同步代码块中的代码执行速度很快的情况下,在ThreadA已经1获得对象锁开始执行同步代码块的时候,ThreadB获取锁失败,开始进行自旋尝试获得锁,直到获取成功,但这样一直自旋也会带来性能上的开销,所以轻量级锁分为自旋锁和自适应自旋锁

  • 自旋锁:一直自旋,直到获取成功
    for(;;){
        // TODO 获得锁
    }

  • 自适应自旋锁:由系统自己判断,若之前对这个锁对象获取成功率都比较高,则认为这一次会获取成功,会继续进行自旋,若之前成功率都很低而且需要很长时间才可以获取到,系统会降低自旋的次数,甚至不进行自旋,直接将线程挂起阻塞
4.3.2 执行流程
  • 首先会先在栈中开辟出来一块空间,存储Lock 的相关信息 (Lock Record)
  • 将Mark Word 中的信息,复制到 Lock Record 当中
  • 将设置好的 Lock Record 赋值到 Mark Word ,并将Lock Record 的 owner 设置为当前锁对象
    • 成功:表示线程已经获取到锁,并将 Mark word 的锁标记设置为 00
    • 失败:若更新失败会首先检查 Mark Word 是否指向当前线程的栈帧,若指向则继续执行,若不是,则表明存在竞争,会膨胀为重量级锁,锁标记为10

96_3.png

  • 执行完毕后,要通过 CAS 将线程栈帧中的 Lock Record 信息,替换回 Mark Word 中,若失败,表示有线程在竞争锁,锁升级为膨胀锁

96_4.png

5、重量级锁
    // 获取到 ObjectMonitor 对象,通过该对象进行加锁
      ObjectMonitor* monitor() const {
        assert(has_monitor(), "check");
        // Use xor instead of &~ to provide one extra tag-bit check.
        return (ObjectMonitor*) (value() ^ monitor_value);
      }

我们通过 javap -v 类名.class 可以看到

96_5.png

monitorenter 指令尝试获取到对象锁

monitorexit 指令对锁进行释放

第二个 monitorexit 在抛出异常时,执行

基本流程就是,每一个 java 对象都会与一个 monitor 监视器对象进行关联,线程A 通过执行 monitorenter 指令,尝试获取 ObjectMonitor 监视器对象,若获取成功,则执行同步代码块,执行完毕后执行 monitorexit 指令,进行 监视器对象的释放,若没有获得 ObjectMonitor 对象,则会将其添加到一个同步队列中,等待之前的线程执行完毕后,释放ObjectMonitro 对象,同时对同步队列进行唤醒,后者再次尝试执行 monitorenter 执行,对监视器对象进行获取。

而加锁解锁的操作,是基于底层操作系统去实现的,像 Linux 是 mutexLock 一个互斥锁,线程在被堵塞后就会进入 内核态等待系统调度,这个就会导致在系统态和内核态之间来回切换,影响效率。

96_6.png

6、wait / notify 线程通信
  • wait:表示放弃当前线程持有的 CPU 资源,并释放锁权限,进入等待状态
  • notify:对进行等待状态的线程进行唤醒,唤醒之后立即获得锁权限
  • notify / notifyAll:前者唤醒一个,立马获得锁权限,后者唤醒全部,竞争锁资源

需要注意的是:三个方法都必须在 synchronized 同步关键字锁限定的作用域中执行 ,否则会抛出java.lang.IllegalMonitorStateException

7、join 操作
  • join 操作可以用来实现线程的顺序执行 ThreadA -> ThreadB -> ThreadC 可以通过 ThreadA.join(),ThreadB().join() 来实现
  • join 的底层也是调用的 wait 方法
public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) { // 如果 线程还存活,调用 wait 方法
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

当线程生命周期结束的时候

static void ensure_join(JavaThread* thread) {
  // We do not need to grap the Threads_lock, since we are operating on ourself.
  Handle threadObj(thread, thread->threadObj());
  assert(threadObj.not_null(), "java thread object must exist");
  ObjectLocker lock(threadObj, thread);
。。。 。。。
  java_lang_Thread::set_thread(threadObj(), NULL);
  // 会调用 notify_all 进行唤醒
  lock.notify_all(thread);
  // Ignore pending exception (ThreadDeath), since we are exiting anyway
  thread->clear_pending_exception();
}

文章永久链接:https://tech.souyunku.com/42841

未经允许不得转载:搜云库技术团队 » 初识 Synchronized

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们