Java中的synchronized锁:从魔法图书馆到高效线程同步

目录

一. 线程安全:为何需要同步?

1. 不可变(Immutable)

2. 绝对线程安全(Absolutely Thread-Safe)

3. 相对线程安全(Conditionally Thread-Safe)

4. 线程兼容(Thread-Compatible)

5. 线程对立(Thread-Opposed)

二. 互斥同步:同步的基础

1.同步的概念

2.互斥的概念

3.临界区

4.互斥量和信号量

5.Java中的互斥同步

同步方法

同步代码块

三. synchronized 特性

1.synchronized 锁的基本性质:

2.synchronized原子性、可见性与有序性

3.synchronized 的主要特点:

4.理解锁的不同性质

悲观锁 vs 乐观锁

共享锁 vs 独占锁(排他锁)

公平锁 vs 非公平锁

可重入锁 vs 不可重入锁

四. synchronized 基础

4.1 synchronized 代码块

4.2 synchronized 方法

4.3 同步代码块与同步方法的使用注意事项

粒度选择

锁对象的选择

五. synchronized 原理

5.1 对象头 & Mark Word

5.2 重量级锁 JVM 层面的实现原理:Monitor

5.2.1 Monitor 结构

5.2.2 Monitor 的获取和释放(重量级锁的获取和释放)

5.3 重量级锁 OS 层面的实现原理:互斥锁

5.3.1 互斥锁

5.3.2 重量级锁开销大的原因

5.4总结

六. synchronized 优化

6.1 锁升级

6.1.1 偏向锁

6.1.2 轻量级锁

6.1.3 自旋锁

6.1.4 偏向锁、轻量级锁、重量级锁的比较

6.1.5 对象信息存在哪

6.2 锁消除

6.3 锁粗化

6.4总结

七. 总结

一. 线程安全:为何需要同步?

想象你正在一座神奇的魔法图书馆中。这座图书馆里有着无数珍贵且独一无二的书籍,每本书都是一个共享资源。如果你和你的朋友们都想同时阅读同一本书,而没有规则来管理你们的行为,那么可能会出现混乱的局面:有人可能会不小心撕破书页,甚至为了抢夺某本书而发生争执。为了避免这种情况,我们需要一种方式来确保每个人都能有序地借阅书籍而不破坏它们。这就是线程安全的重要性所在。

在Java语言中,当多个线程访问一个对象时,如果不需要考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步操作或调用方进行任何其他的协调操作,就能获得正确的结果,那么这个对象就是线程安全的。

根据线程安全的程度,我们可以将Java语言中的各种操作共享数据分类如下,按安全程度由强到弱排序,并结合魔法图书馆的例子进行说明:

1. 不可变(Immutable)

在魔法图书馆中,有些书籍是被施了魔法的,一旦写成便无法更改内容。这些书籍就像是不可变的对象,无论多少人同时阅读,都不会改变书中的内容。

不可变的对象一定是线程安全的。无论是对象的方法实现还是方法的调用者都不需要采取额外的安全措施。如果共享的数据是一个基本数据类型,只需在定义时使用final关键字修饰即可保证其不可变。对于对象而言,只要该对象的状态在其生命周期内不会发生变化,它就是不可变的。例如,String类就是不可变的典型例子。

2. 绝对线程安全(Absolutely Thread-Safe)

假设图书馆中有一本魔法书,这本书被施了强大的保护咒语,无论有多少读者同时尝试阅读或修改它,都不会出现问题。然而,这种级别的保护非常昂贵且难以实现。

这种类型的对象无论在何种运行环境下都是线程安全的,调用者无需采取任何额外的同步措施。然而,实现这种级别的线程安全通常需要付出很大的代价,有时甚至是不切实际的。

3. 相对线程安全(Conditionally Thread-Safe)

你可以安全地借阅一本书,但如果想要同时借阅多本书并做些复杂的操作(比如比较两本书的内容),你就需要自己确保操作的顺序和同步。例如,如果你同时借阅两本关于魔法咒语的书籍并试图对比它们的效果,你需要小心处理,以避免混淆或错误。

这是通常意义上所说的线程安全。这类对象确保每个单独的操作是线程安全的,但组合操作可能不是。Java中许多声称线程安全的类,如Vector、Hashtable等,都属于这一类。

4. 线程兼容(Thread-Compatible)

虽然每个人都可以自由地进入图书馆借阅书籍,但如果想要避免冲突(比如两个人同时想借同一本书),就需要你自己制定一些规则来管理借阅行为。例如,你可以设定一个规则:每次只能一个人借阅某本书,其他人必须等待。

这类对象本身并不是线程安全的,但可以通过调用端正确使用同步手段来保证对象的安全使用。大多数标准集合类,如ArrayList和HashMap,都属于此类。

5. 线程对立(Thread-Opposed)

有些特殊的书籍极其脆弱,任何形式的并发访问都会导致损坏。因此,必须完全禁止多人同时访问这些书籍。例如,一本记载着古老而危险魔法的书,任何同时打开它的尝试都会引发灾难性的后果。

无论调用端是否采取了同步措施,都无法在多线程环境中并发使用代码。这类情况非常罕见,但在某些特定的场景下确实存在。

. 互斥同步:同步的基础

在魔法图书馆中,为了确保每位读者都能顺利借阅书籍,管理员设置了一个规则:每次只能有一位读者可以借阅某本特定的书籍。这就像Java中的互斥同步机制,确保同一时刻只有一个线程可以访问特定资源。这样就能避免多个线程同时修改同一个数据导致的数据不一致问题。

1.同步的概念

同步指的是在多个线程并发访问共享数据时,保证共享数据在同一个时刻只能被一个线程使用。想象一下,如果多位读者同时试图借阅同一本书,可能会导致书页被撕破或内容被错误地修改。为了避免这种情况,我们需要一种机制来确保在同一时间只有一位读者能够借阅这本书。

2.互斥的概念

互斥是实现同步的一种手段,它确保在任意时刻只有一个线程能够进入临界区(即需要保护的代码段)。在魔法图书馆的例子中,临界区就是那本特定的书籍,而互斥量则是管理员制定的规则,确保每次只有一个读者可以借阅该书。

3.临界区

临界区是指那些必须互斥执行的代码段,这些代码段操作共享资源,如果不加以控制,可能会引发数据不一致的问题。在魔法图书馆中,临界区可以比作某一本特定的书籍,只有持有特殊钥匙(锁)的读者才能进入这个区域进行借阅。

4.互斥量和信号量

互斥量(Mutex):互斥量是一种特殊的锁,用于确保同一时刻只有一个线程能够访问某个资源。在魔法图书馆中,互斥量就像是管理员手中的唯一一把钥匙,只有拿到这把钥匙的读者才能借阅特定的书籍。

    synchronized(this) {

      // 临界区:每次只能有一个线程进入这里

      System.out.println("This is a critical section.");

  }

信号量(Semaphore):信号量是一种更灵活的同步工具,它可以控制同时访问某一资源的线程数量。在魔法图书馆中,信号量可以用来管理同时进入图书馆的读者数量。例如,如果图书馆规定最多只能有5个人同时在里面阅读,那么信号量就可以用来控制这一点。

Semaphore semaphore = new Semaphore(5); // 最多允许5个线程同时访问

  semaphore.acquire(); // 请求许可

  try {

      // 临界区:最多5个线程可以同时在这里

      System.out.println("Accessing shared resource.");

  } finally {

      semaphore.release(); // 释放许可

  }

5.Java中的互斥同步

在Java中,synchronized关键字是最常用的实现互斥同步的方式之一。它可以帮助我们轻松地确保同一时刻只有一个线程能够访问某个方法或代码块。

同步方法
public class Book {

    public synchronized void borrowBook() {

        // 临界区:每次只能有一个线程进入这里

        System.out.println(Thread.currentThread().getName() + " is borrowing the book.");

    }

}

在这个例子中,borrowBook方法被声明为同步方法,这意味着每次只能有一个线程能够调用这个方法。其他试图调用该方法的线程将被阻塞,直到当前线程完成操作并释放锁。

同步代码块

有时候,我们不需要同步整个方法,只需要同步其中的一部分代码。这时可以使用同步代码块:

public class Book {

    private final Object lock = new Object();

    

    public void borrowBook() {

        synchronized(lock) {

            // 临界区:每次只能有一个线程进入这里

            System.out.println(Thread.currentThread().getName() + " is borrowing the book.");

        }

    }

}

在这个例子中,我们定义了一个私有的锁对象,并在需要同步的代码块上使用了synchronized关键字。这样做可以减少锁的粒度,从而提高并发性能。

. synchronized 特性

在魔法图书馆中,每个房间(资源)都有一个对应的锁,只有持有正确的钥匙(锁)才能进入房间。synchronized 就像这个魔法钥匙串,确保了线程安全的同时提供了许多有用的特性。

1.synchronized 锁的基本性质:

悲观锁:在魔法图书馆中,每位读者都认为其他人可能会抢走自己想读的书,因此每次借阅都需要先获得管理员的许可。同样,synchronized 是悲观锁,每次使用共享资源时都认为会和其他线程产生竞争,所以每次使用共享资源都会上锁。

独占锁(排他锁):每次只能有一位读者可以借阅某本特定的书籍。这意味着该锁一次只能被一个线程所持有,其他线程被阻塞。在Java中,synchronized 是独占锁(排他锁),确保同一时刻只有一个线程能够访问特定资源。

非公平锁:在魔法图书馆中,有时候管理员可能会优先处理那些刚刚到达的读者,而不是严格遵循先来后到的顺序。这就像 synchronized 是非公平锁,线程获取锁的顺序可以不按照线程的阻塞顺序。

可重入锁:如果一位读者已经借到了一本书,如果他还想借另一本书,可以直接去借,不需要重新排队。在Java中,synchronized 是可重入锁,持锁线程可以再次获取自己的内部的锁。

2.synchronized原子性、可见性与有序性

synchronized 如何确保原子性、可见性和有序性:

原子性:在魔法图书馆中,如果你是唯一持有某本书的读者,那么你可以自由地阅读这本书而不用担心其他人干扰。同样,synchronized 确保只有一个线程能够拿到锁并进入同步代码块操作共享资源,因此具有原子性。

可见性:当你在魔法图书馆中借阅一本书时,任何对书的修改(如添加注释)都会立即被其他读者看到。在Java中,synchronized 保证共享变量的修改能够及时可见。执行 synchronized 时,会对应执行 lock 和 unlock 原子操作。lock 操作会清空工作空间中该变量的值;执行 unlock 操作之前,必须先把变量同步回主内存中。

有序性:在魔法图书馆中,即使多个读者同时试图借阅书籍,管理员也会确保每个人按顺序进入和离开。在Java中,synchronized 内的代码和外部的代码禁止排序,至于内部的代码,则不会禁止排序,但是由于只有一个线程进入同步代码块,因此在同步代码块中相当于单线程的。根据 as-if-serial 语义,即使代码块内发生了重排序,也不会影响程序执行的结果。

3.synchronized 的主要特点:

1.简单:只需使用简单的关键词即可。

   public synchronized void borrowBook() {

       System.out.println(Thread.currentThread().getName() + " is borrowing the book.");

2.自动释放锁:当线程执行完同步代码块或方法后,锁会自动释放,无需手动管理。

   synchronized(this) {

       // 临界区:每次只能有一个线程进入这里

       System.out.println("This is a critical section.");

   }

3.可重入性:同一个线程可以多次获取同一把锁而不造成死锁。例如,一个线程可以在持有锁的情况下调用另一个需要相同锁的方法。

4.理解锁的不同性质

在魔法图书馆的世界里,不同类型的锁就像管理员使用不同的规则来管理读者借阅书籍的行为。下面让我们来理解这些锁的特性:

悲观锁 vs 乐观锁

悲观锁:在魔法图书馆中,如果你认为每当你想借一本书时,其他人都可能也想借同一本书,那么你会每次借书前都先向管理员申请一把特殊的钥匙(锁)。这种总是假设会有竞争的情况,就像Java中的悲观锁。每次访问共享资源时都认为会和其他线程产生竞争,所以每次都会上锁。你走进图书馆,看到一本热门书籍,立即去找管理员要了一把特殊钥匙,以防其他人也来借这本书。

乐观锁:相反,如果图书馆里的大多数书籍都不太受欢迎,你可能会选择直接去拿书,只有在准备离开时才检查是否有其他人也在修改这本书。这种策略假设不会有冲突,只在提交更改时检查是否有冲突,这就是乐观锁的工作方式。你随意拿起一本书阅读,但只有当你打算归还并做些笔记时,才会检查是否有人在你之前做了改动。

共享锁 vs 独占锁(排他锁)

共享锁:假设图书馆允许多个读者同时阅读同一本书,但不允许任何人在此期间做任何修改。这就像是共享锁,允许多个线程同时读取数据,但写操作需要独占锁。你可以和朋友们一起阅读同一本魔法书,但没有人可以在这段时间内添加注释或撕毁书页。

独占锁(排他锁):如果你想对某本书做详细的批注或者修复破损的部分,你需要确保在整个过程中没有其他人能接触到这本书。这就像独占锁,一次只能被一个线程所持有,其他线程被阻塞。你找到管理员,要求单独使用某个房间来修复一本珍贵的魔法书,在你完成之前,任何人都不能进入这个房间。

公平锁 vs 非公平锁

公平锁:在某些特别严格的图书馆中,管理员严格按照先来后到的原则分配书籍,每个人都要排队等待轮到自己。这种机制类似于公平锁,它严格按照线程的等待时间顺序分配锁。所有读者按照到达图书馆的时间排队,依次借阅他们想要的书籍。

非公平锁:而在另一些图书馆中,管理员可能会优先处理那些刚刚到达的读者,而不是严格遵循先来后到的顺序。这种情况类似于非公平锁,线程获取锁的顺序可以不按照线程的阻塞顺序。有时候新来的读者可能会插队借阅某本不太热门的书,而那些已经等了很久的人则需要继续等待。

可重入锁 vs 不可重入锁

可重入锁:如果你在魔法图书馆中已经借到了一本书,并且还想再借另一本书,你不需要重新排队申请新的钥匙,而是可以直接去借。这种机制就像可重入锁,持锁线程可以再次获取自己的内部的锁。你已经拿到了一本魔法书的钥匙,现在想要再借另一本书,你可以直接去借,而不需要再去管理员那里申请新的钥匙。

不可重入锁:相比之下,有些图书馆规定,即使你已经借到了一本书,如果你想再借另一本书,必须重新排队申请新的钥匙。这种情况类似于不可重入锁,不允许同一个线程多次获取同一个锁。即使你已经借到了一本书,如果你想再借另一本书,必须重新排队申请新的钥匙,否则无法借阅第二本书。

. synchronized 基础

在魔法图书馆中,synchronized 的基础可以被理解为一种“钥匙管理系统”,它确保每位读者(线程)在借阅书籍(访问共享资源)时能够有序进行。下面我们通过具体的代码示例和生动的比喻来深入探讨 synchronized 的两种主要使用方式及其注意事项。

4.1 synchronized 代码块

public void method() {

    synchronized(this) { // 这里是同步代码块

        System.out.println("This is a synchronized block.");

    }

}

在魔法图书馆中,当你想要借阅一本特别珍贵的书籍时,你需要先找到管理员,他会给你一把特殊的钥匙,让你进入一个小房间,在那里你可以安静地阅读这本书。一旦你离开房间,钥匙就会自动回到管理员手中。synchronized 代码块允许我们精确指定需要保护的代码范围。锁对象由 synchronized 后面括号中的对象决定(如上述代码中的 this)。每个线程在进入同步代码块之前必须获得该锁对象的控制权。当线程执行完同步代码块后,锁会自动释放,其他线程才能继续获取锁并进入。

优势:

粒度更小:只锁定必要的代码段,而不是整个方法,从而提高并发性能。

灵活性高:可以选择不同的锁对象,避免不必要的阻塞。

4.2 synchronized 方法

public synchronized void method() { // 这是一个同步方法

    System.out.println("This is a synchronized method.");

}

如果你把整个房间都锁住了,那么只有持有正确钥匙的人才能进入任何部分。如果整个房间代表一个方法,那么只有持有正确钥匙的人才能进入并操作其中的所有内容。对于实例方法,锁对象是当前实例(this)。对于静态方法,锁对象是类的 Class 对象。同步方法会锁定整个方法体,确保同一时刻只有一个线程能够调用该方法。

适用场景:

如果方法体内的所有操作都需要同步保护,则使用同步方法更为简洁。但如果方法体很大且并非所有代码都需要同步,同步方法可能会导致性能下降。

4.3 同步代码块与同步方法的使用注意事项

粒度选择

尽量减小同步范围以提高并发性能:

在魔法图书馆中,这意味着你只需要为那些真正需要保护的部分申请钥匙,而不是整座图书馆。在代码中,尽量只锁住必要的代码段,而不是整个方法。例如:

    public void updateBookDetails(Book book) {

        // 非同步代码

        System.out.println("Preparing to update book details...");

        

        // 同步代码块

        synchronized(book) {

            System.out.println("Updating book details...");

        }

        

        // 非同步代码

        System.out.println("Finished updating book details.");

    }

    在这个例子中,只有更新书籍细节的操作被同步,而其他操作可以在不同线程中同时执行。

锁对象的选择

明确指定锁对象有助于减少不必要的阻塞:

在魔法图书馆中,这就像为不同种类的书籍设置了不同的钥匙系统,使得不同类型的书籍可以被不同的读者同时借阅。在代码中,可以根据实际需求选择合适的锁对象。例如:

使用 this 锁住当前实例。

使用某个特定的对象作为锁(如 private final Object lock = new Object();)。

使用类的 Class 对象锁住静态方法或静态变量。

  private final Object lock1 = new Object();

  private final Object lock2 = new Object();



  public void methodA() {

      synchronized(lock1) {

          System.out.println("Method A is running with lock1.");

      }

  }



  public void methodB() {

      synchronized(lock2) {

          System.out.println("Method B is running with lock2.");

      }

  }

  在这个例子中,methodA 和 methodB 使用了不同的锁对象,因此它们可以被不同的线程同时执行,而不会相互阻塞。

. synchronized 原理

为了更深入地理解 synchronized 的工作原理,我们需要探索其在 JVM 和操作系统层面的具体实现。通过魔法图书馆的比喻,我们可以更加形象化地理解这些复杂的概念。

5.1 对象头 & Mark Word

每个 Java 对象都有一个“头部”,里面包含了一个叫 Mark Word 的部分,用于存储锁的状态信息。在魔法图书馆中,你可以把这个想象成一把多功能钥匙链,上面有不同的标记来表示当前的状态(如是否已借出、谁正在借阅等)。Mark Word 是对象头的一部分,它存储了对象的哈希码、年龄、锁状态标志以及其他一些信息。在不同的锁状态下,Mark Word 中存储的信息会有所不同。例如,在无锁状态下,它可能存储的是对象的哈希值;而在轻量级锁状态下,则存储指向锁记录的指针。

5.2 重量级锁 JVM 层面的实现原理:Monitor

5.2.1 Monitor 结构

Monitor 就像是一个智能门禁系统,它知道谁在房间里,并能控制谁可以进入。在魔法图书馆中,管理员就像是这个智能门禁系统,他记录着谁进入了哪个房间,并决定是否允许其他人进入。Monitor 是一种同步机制,通常与 synchronized 关键字一起使用。每个 Java 对象都关联有一个 Monitor,当线程试图获取锁时,实际上是尝试获取该对象的 Monitor。Monitor 包含多个字段,如 Entry Set(等待进入的线程集合)、Owner(当前持有锁的线程)和 Wait Set(等待条件满足的线程集合)。

5.2.2 Monitor 的获取和释放(重量级锁的获取和释放)

获取和释放锁的过程涉及复杂的操作系统调用,这使得其开销较大。就像每次进出都需要刷卡登记一样,效率不高。在魔法图书馆中,这意味着每次借阅都需要管理员亲自为你开门关门,过程较为繁琐当线程尝试获取 Monitor 时,如果 Monitor 已被其他线程占用,当前线程将被阻塞并放入 Entry Set 中等待。当线程释放 Monitor 时,JVM 会检查 Entry Set 中是否有等待的线程,并选择一个线程唤醒以继续执行。这种机制涉及到操作系统级别的上下文切换,因此开销较大。

5.3 重量级锁 OS 层面的实现原理:互斥锁

5.3.1 互斥锁

互斥锁通过操作系统级别的原语来实现,这增加了上下文切换的成本。想象一下每次进出都要换衣服的感觉。在魔法图书馆中,这就好比每次借阅书籍都需要更换专门的服装,增加了时间成本。互斥锁是一种低级同步机制,通常由操作系统提供。当线程获取互斥锁时,如果锁已被其他线程持有,当前线程将被阻塞,直到锁被释放。这种机制会导致线程上下文切换,从而增加开销。

5.3.2 重量级锁开销大的原因

由于涉及到操作系统级别的调用,因此它的开销远大于轻量级锁。就好比每次都请专业人士来帮你开门关门,费用自然更高。在魔法图书馆中,这意味着管理员需要花费更多的时间来处理借阅请求,降低了整体效率。重量级锁依赖于操作系统的调度器进行线程管理,导致较高的上下文切换开销。相比之下,轻量级锁(如偏向锁、轻量级锁)在用户态下即可完成,不需要频繁的内核态切换,因此性能更好。

5.4总结

对象头 & Mark Word:像是一把多功能钥匙链,存储着对象的各种状态信息。

Monitor 结构:类似于图书馆的智能门禁系统,负责管理谁可以进入哪个房间(即哪个线程可以访问哪个资源)。

Monitor 的获取和释放:就像每次进出都需要管理员手动开门关门,效率较低。

互斥锁:如同每次借阅书籍都需要更换专门的服装,增加了时间成本。

. synchronized 优化

 

为了优化锁的性能,JVM 提供了锁升级机制。通过这种机制,锁可以从偏向锁逐步升级到轻量级锁,甚至重量级锁,以适应不同的并发场景。接下来,我们来详细解释这些锁升级的过程。

6.1 锁升级

6.1.1 偏向锁

偏向锁旨在消除无竞争情况下的同步开销,提升性能。在魔法图书馆中,如果每次都是同一个人来借书,那么直接给他一把永久钥匙岂不是更方便?

获取偏向锁:当第一个线程访问时,直接将锁偏向给该线程。就像给了他一把专属钥匙。在魔法图书馆中,这意味着管理员会直接给经常借阅某类书籍的读者发放专属钥匙,简化了借阅流程。

  // 假设某个线程首次访问一个对象

  synchronized(obj) {

      // 锁偏向给当前线程

  }

释放偏向锁:只有当其他线程试图获取锁时,才会撤销偏向锁。相当于发现不止一个人要用这本书时,才收回那把专属钥匙。在魔法图书馆中,这意味着当有其他读者也需要借阅这本书时,管理员会暂时收回专属钥匙,改为普通借阅模式。

  // 当另一个线程尝试访问同一个对象

  synchronized(obj) {

      // 撤销偏向锁,转为轻量级锁或重量级锁

  }
6.1.2 轻量级锁

轻量级锁适用于短时间内的锁竞争场景。就像临时借用一本书,只需要快速做个记录即可。在魔法图书馆中,这意味着对于那些只是短暂借阅的读者,管理员会做一个简单的记录,而不是进行复杂的手续。

获取轻量级锁:通过CAS(Compare and Swap)操作尝试获取锁。类似于快速记下借书人的名字。在魔法图书馆中,管理员会迅速记录下借阅者的姓名,以便于后续管理。

  // 使用CAS操作尝试获取锁

  synchronized(obj) {

      // CAS操作成功则获取轻量级锁

  }

释放轻量级锁:如果成功,则直接解锁;否则,膨胀为重量级锁。就像还书时检查是否需要做进一步处理。在魔法图书馆中,这意味着在归还书籍时,管理员会检查是否有其他读者也在等待借阅,必要时采取更严格的管理措施。

 // 如果CAS操作失败,则膨胀为重量级锁

  synchronized(obj) {

      // 进入重量级锁状态

  }
6.1.3 自旋锁

自旋锁是在等待锁的过程中不断循环检查锁是否可用,而不是立即进入阻塞状态。这就像站在门外不停敲门直到主人开门为止。在魔法图书馆中,读者会在门口等待,不断地询问管理员是否可以进入,而不是直接离开去做其他事情。

// 自旋锁示例(伪代码)

while (!lock.isAvailable()) {

    // 不断检查锁是否可用

}
6.1.4 偏向锁、轻量级锁、重量级锁的比较

在魔法图书馆中,偏向锁适合那些经常独自借阅某一类书籍的读者;轻量级锁适合偶尔借阅某些书籍的读者;而重量级锁则适用于那些需求量大、竞争激烈的热门书籍。

6.1.5 对象信息存在哪

对象的信息主要存在于堆内存中,而锁的状态则保存在Mark Word内。可以把对象想象成一个文件夹,里面存放着各种信息,而Mark Word就是文件夹上的标签。在魔法图书馆中,每本书就是一个对象,而Mark Word则是书上贴着的标签,标明了这本书当前的状态。

6.2 锁消除

编译器能识别某些无法被多个线程同时访问的同步块,并将其优化掉。 这就像自动识别那些根本不需要锁的地方,然后移除多余的锁。在魔法图书馆中,这意味着管理员能够识别出哪些书籍实际上不需要特别保护,从而简化借阅流程。

// 锁消除示例(伪代码)

public void method() {

    synchronized(this) { // 编译器识别到此处无需同步,自动优化

        // 不需要同步的操作

    }

}

6.3 锁粗化

将多个连续的小同步块合并成一个大同步块,减少锁的开销。 就像把多个小任务合并成一个大任务一起完成,这样就不用频繁地加锁和解锁。在魔法图书馆中,这意味着对于那些需要连续借阅多本书籍的任务,管理员可以一次性处理所有借阅请求,而不是逐个处理。

// 锁粗化示例(伪代码)

public void method() {

    synchronized(this) {

        // 多个连续的同步操作被合并为一个大的同步块

    }

}

6.4总结

通过魔法图书馆的比喻,我们可以更加形象化地理解 synchronized锁的升级机制:

偏向锁:针对无竞争场景,直接给常客发放专属钥匙,简化借阅流程。

轻量级锁:适用于短暂的竞争,通过CAS操作快速记录借阅者信息。

重量级锁:用于长期竞争,涉及操作系统级别的调用,效率较低。

锁消除与锁粗化:通过编译器优化,识别并移除不必要的锁,或将多个小同步块合并为一个大同步块,减少锁的开销。

七. 总结

在魔法图书馆中,synchronized 锁机制通过偏向锁、轻量级锁和重量级锁的逐步升级,巧妙地应对了从无竞争到高竞争的不同场景,确保了数据的一致性和线程的安全性。偏向锁如同给常客发放专属钥匙,简化了无竞争情况下的操作;轻量级锁利用CAS操作快速处理短暂的竞争;而重量级锁则通过操作系统级别的同步机制管理长期的竞争,尽管开销较大但保证了资源的有效保护。此外,锁消除与锁粗化等优化手段进一步提升了程序性能,减少了不必要的同步开销。这些机制共同作用,使得 synchronized 成为保障多线程环境下高效且安全访问共享资源的关键工具。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值