Java基础夯实——2.6 Java中锁

1 Java中锁的概念

锁用于控制多个线程对共享资源的访问。只有持有锁的线程才能访问被保护的资源,其他线程必须等待锁的释放。这种机制可以防止线程之间的竞争条件(Race Condition)。保证了同一时刻只有一个线程持有对象的锁并修改该对象,从而保障数据的安全。java中的锁包括

2 乐观锁和悲观锁

悲观锁

认为"冲突总是会发生",在访问共享资源之前,线程会先获取锁,以确保只有当前线程能够访问资源,其他线程需要等待锁的释放。它适合竞争激烈、数据一致性要求高的场景,如数据库操作、银行转账等。在 Java 中,悲观锁的典型实现是通过 synchronizedReentrantLocksynchronized 是 Java 提供的内置锁,易于使用,但功能相对有限,主要用于方法或代码块的加锁保护。而 ReentrantLock 是显式锁,可以手动控制锁的获取和释放,并提供更高级的功能,如公平锁、非阻塞获取锁(tryLock())以及响应中断(lockInterruptibly())。由于悲观锁的加锁行为会导致线程阻塞,它可能带来性能开销,特别是在锁竞争激烈的场景中,可能会因为线程等待导致上下文切换频繁。

乐观锁

假设"冲突很少发生",它通过无锁的方式来完成线程间的资源控制,并依赖版本号机制或 CAS(Compare-And-Swap,比较并交换)操作来实现。CAS 是一种硬件级别的原子操作,它会比较当前值是否符合预期值,如果符合则更新,不符合则重试。在 Java 中,AtomicIntegerAtomicReference 等原子类就是基于 CAS 实现的。除了 CAS,乐观锁还可以通过版本号机制解决更复杂的业务场景,例如数据库更新中,读取某条记录的版本号,在提交时检查版本号是否与读取时一致,如果一致则更新,否则说明资源已被修改,需要重试或放弃。这种方式适合读多写少的场景,比如计数器累加、缓存刷新等,因为在这些场景中,数据的冲突发生概率低,使用乐观锁可以避免传统锁的阻塞问题,从而显著提高性能。

总体来看,悲观锁适合高冲突、数据一致性要求严格的场景,而乐观锁适合低冲突、高性能需求的场景。在实际应用中,悲观锁更容易实现且更安全,因为它完全避免了资源争用的问题,但需要付出一定的性能代价。而乐观锁则需要在实现中精心设计冲突处理逻辑,对于低冲突的场景能提供更高的吞吐量,但在高冲突场景下可能因为重试而导致性能下降。如果对性能要求较高且冲突概率低,可以选择乐观锁;而对数据一致性要求高时,应优先考虑悲观锁。

3 自旋锁(Spin Lock)

是一种轻量级的锁实现方式,其特点是线程在尝试获取锁时不会立即进入阻塞状态,而是通过循环不断尝试获取锁,直到成功为止。与传统的悲观锁不同,自旋锁假设线程会在较短的时间内释放锁,因此通过让线程"自旋"(占用 CPU 轮询)来等待锁的释放,而不是进入操作系统层面的阻塞状态。

自旋锁的核心思想 是减少线程上下文切换的开销。在传统的悲观锁(如 synchronizedReentrantLock)中,如果线程未能获得锁,往往会进入等待队列并触发线程的挂起和唤醒操作,这些操作涉及操作系统的内核态转换,开销较高。而自旋锁通过线程不断尝试获取锁的方式,避免了这些系统调用的开销,适合锁的持有时间非常短的场景。

在 Java 中,自旋锁的实现通常基于 CAS(Compare-And-Swap)指令,例如 AtomicReferenceUnsafe 类。以下是一个简单的自旋锁实现示例:

java 复制代码
import java.util.concurrent.atomic.AtomicReference;

public class SpinLock {
    private final AtomicReference<Thread> owner = new AtomicReference<>();

    public void lock() {
        Thread currentThread = Thread.currentThread();
        // 不断尝试获取锁
        while (!owner.compareAndSet(null, currentThread)) {
            // 自旋等待
        }
    }

    public void unlock() {
        Thread currentThread = Thread.currentThread();
        // 释放锁
        owner.compareAndSet(currentThread, null);
    }
}

在上面的例子中,lock 方法使用 CAS 操作来尝试将锁的拥有者设置为当前线程,如果失败,则进入自旋。unlock 方法通过 CAS 将锁的拥有者重置为 null

自旋锁的优点 是避免了线程上下文切换的开销,因此在锁的持有时间非常短、线程竞争不激烈的情况下性能较好,比如在多核 CPU 上短时间临界区的保护场景。但缺点也非常明显:自旋期间线程会占用 CPU 资源,因此当锁的持有时间较长或线程竞争较多时,自旋锁会导致大量的 CPU 资源浪费,反而降低系统性能。

在实际应用中,自旋锁往往配合一定的超时时间或重试次数来防止无限自旋。例如,尝试自旋若干次后仍未获得锁,则线程会选择进入阻塞状态,以避免长时间占用 CPU 资源。在 Java 的 ReentrantLock 实现中,自旋锁是其锁实现的一部分,用于优化轻量级的锁争用。此外,JDK 的 LockSupport 类也提供了类似的工具来帮助实现更复杂的自旋锁机制。

总结来说,自旋锁适用于锁持有时间短、竞争少的场景,如多线程计算中的局部变量保护。但对于锁竞争激烈或持有时间长的场景,自旋锁可能适得其反,应选择悲观锁或其他锁机制。

4 synchronized

synchronized 是 Java 提供的一种内置同步机制,用于解决多线程环境下的共享资源访问问题。它通过对象的**监视器锁(Monitor Lock)**来确保线程对共享资源的互斥访问,是最基础的线程同步手段。

4.1 synchronized 是什么?

synchronized 是 Java 的关键字,用于实现线程的互斥锁同步机制 。它可以修饰代码块、方法或类,保证同一时刻只有一个线程能够执行被 synchronized 修饰的代码,从而解决线程安全问题。

在底层,synchronized 是通过对象头中的监视器锁(Monitor)实现的。每个 Java 对象都有一个内置的锁,也称为对象锁 。线程在进入 synchronized 代码块时会尝试获取该锁,获取成功后才能执行代码,执行完成后释放锁。其他线程在锁被占用时会进入等待状态。

4.2 synchronized 的用法

  1. 修饰实例方法

    为实例方法加锁,锁定的是当前对象实例,确保同一时间只有一个线程可以调用该实例的同步方法。

    java 复制代码
    public class Counter {
        private int count = 0;
    
        public synchronized void increment() {
            count++;
        }
    }

    如果两个线程访问同一个 Counter 对象的 increment() 方法,会进行互斥控制。

  2. 修饰静态方法

    为静态方法加锁,锁定的是类对象(Class 对象),确保同一时间只有一个线程可以调用该类的同步静态方法。

    java 复制代码
    public class Counter {
        private static int count = 0;
    
        public static synchronized void increment() {
            count++;
        }
    }

    静态方法锁的范围是全局的,不同实例调用相同的同步静态方法也会互斥。

  3. 修饰代码块

    为指定的代码块加锁,可以更加灵活地控制锁的粒度,锁定的是 synchronized 括号中的对象。

    java 复制代码
    public void increment() {
        synchronized (this) {
            count++;
        }
    }

    使用代码块锁时,可以选择更细粒度的锁定范围,避免锁的范围过大影响性能。

4.3 synchronized 的底层原理

synchronized 的底层实现依赖于对象头中的Monitor。Java 对象头(Object Header)中包含了锁的相关信息(Mark Word)。其具体机制如下:

  • 进入临界区:线程尝试获取 Monitor 锁,若获取成功,则进入临界区执行代码;否则进入等待状态。
  • 退出临界区:线程释放 Monitor 锁,唤醒其他正在等待的线程。
  • JVM 支持:Monitor 是由 JVM 实现的,具体依赖 CPU 指令中的 CAS(Compare-And-Swap)和锁状态标志位。

在 JDK 1.6 之后,为了优化 synchronized 的性能,JVM 引入了多种锁优化策略:

  1. 偏向锁:偏向于第一个获取锁的线程,减少加锁和解锁的开销。
  2. 轻量级锁:在锁竞争不激烈时,通过 CAS 实现轻量级锁,避免线程阻塞。
  3. 重量级锁:当线程竞争激烈时,升级为重量级锁,线程进入等待队列,阻塞等待锁释放。

4.4 synchronized 的特点

  1. 互斥性 :同一时间只有一个线程能够执行被 synchronized 修饰的代码,其他线程必须等待锁的释放。

  2. 可重入性 :一个线程可以多次获取同一个对象的锁,而不会发生死锁。这是因为每次锁的获取都会增加锁的计数器,释放锁时计数器递减。

    java 复制代码
    public synchronized void methodA() {
        methodB(); // 同一线程可重入
    }
    
    public synchronized void methodB() {
        // ...
    }
  3. 阻塞性:当一个线程占用锁时,其他线程会进入阻塞状态,等待锁的释放。

  4. 自动释放 :线程退出 synchronized 代码块或方法时,会自动释放锁。

  5. 锁的范围

    • 实例锁 :修饰实例方法或 synchronized(this) 锁定当前对象实例。
    • 类锁 :修饰静态方法或 synchronized(ClassName.class) 锁定类对象。

4.5 synchronized 的优缺点

优点

  • 简单易用synchronized 的语法简单,开发者无需关心锁的释放,易于维护。
  • 安全可靠:内置锁由 JVM 实现,使用得当时不会发生死锁、锁泄露等问题。
  • 性能优化:在 JDK 1.6 之后,通过偏向锁、轻量级锁等机制,提升了性能。

缺点

  • 阻塞开销:线程在等待锁时会阻塞,导致上下文切换开销较大。
  • 全局影响:锁范围不合理可能会降低系统的并发性能。
  • 无法尝试获取锁 :与 ReentrantLock 不同,synchronized 不支持尝试加锁(如 tryLock())或中断锁的获取。

4.6 使用 synchronized 注意事项

  1. 锁粒度要小:避免锁定过大的代码块,减少线程竞争的范围。

    java 复制代码
    // 不推荐:锁住整个方法
    public synchronized void process() { ... }
    // 推荐:仅锁住需要保护的部分
    public void process() {
        synchronized (this) {
            // 临界区
        }
    }
  2. 避免死锁:如果多个线程需要持有多个锁,务必确保锁的获取顺序一致。

  3. 偏向锁优化:对于没有多线程竞争的场景,可以尽量保持锁偏向,减少性能损耗。

5 ReentrantLock

ReentrantLock 是 Java 并发包 (java.util.concurrent.locks) 中提供的一种显式锁(Explicit Lock),功能比 synchronized 更加丰富和灵活。它提供了可重入性、可中断性、超时锁获取等特性,是实现复杂同步需求的重要工具。

5.1 ReentrantLock 是什么?

ReentrantLock 是一个可重入锁,其作用和 synchronized 类似,都是用来解决多线程访问共享资源的线程安全问题。与 synchronized 不同的是,ReentrantLock 是显式的,需要手动加锁和释放锁,具有更强的灵活性。

可重入性 是指一个线程可以多次获取同一把锁而不会导致死锁。ReentrantLock 在获取锁时会记录线程获取锁的次数,释放锁时则需要与获取锁次数匹配。

5.2 ReentrantLock 的基本用法

以下是 ReentrantLock 的基本使用方式:

java 复制代码
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;

    public void increment() {
        lock.lock(); // 获取锁
        try {
            count++;
        } finally {
            lock.unlock(); // 确保锁释放
        }
    }
}

关键点

  1. lock() 用于获取锁,线程在未获取到锁时会阻塞。
  2. unlock() 必须在 try-finally 块中调用,确保锁在任何情况下都能被释放。

5.3 ReentrantLock 的主要特性

  1. 显式加锁与解锁

    synchronized 的隐式加锁方式不同,ReentrantLock 必须显式调用 lock()unlock() 方法。虽然显式加锁略显繁琐,但提供了更多灵活性。

  2. 可重入性

    synchronized 类似,ReentrantLock 是可重入的。同一线程可以多次获取锁,每次获取锁都会增加一个计数器,释放锁时计数器减 1,直到计数器归零,锁才被真正释放。

    java 复制代码
    public void methodA() {
        lock.lock();
        try {
            methodB(); // 可重入调用
        } finally {
            lock.unlock();
        }
    }
    
    public void methodB() {
        lock.lock();
        try {
            // 临界区代码
        } finally {
            lock.unlock();
        }
    }
  3. 公平锁与非公平锁
    ReentrantLock 支持公平锁和非公平锁(默认是非公平锁)。

    • 公平锁:按照线程请求锁的顺序分配锁,先请求先获取,避免线程"饥饿"。
    • 非公平锁:可能会插队,线程在尝试获取锁时直接竞争锁,提高吞吐量,但可能导致某些线程长期得不到锁。
    java 复制代码
    ReentrantLock fairLock = new ReentrantLock(true); // 公平锁
    ReentrantLock unfairLock = new ReentrantLock(false); // 非公平锁
  4. 中断锁获取

    使用 lockInterruptibly() 方法,线程在尝试获取锁时可以响应中断。适合处理线程需要取消或超时的场景。

    java 复制代码
    try {
        lock.lockInterruptibly();
        // 临界区代码
    } catch (InterruptedException e) {
        // 响应中断
    } finally {
        lock.unlock();
    }
  5. 尝试获取锁
    tryLock() 方法允许线程尝试获取锁,如果无法立即获取锁,则可以直接返回失败,而不会阻塞线程。

    java 复制代码
    if (lock.tryLock()) {
        try {
            // 获取锁成功,执行临界区代码
        } finally {
            lock.unlock();
        }
    } else {
        // 获取锁失败,执行其他操作
    }

    tryLock(long timeout, TimeUnit unit) 还支持超时等待锁的功能。

    java 复制代码
    if (lock.tryLock(5, TimeUnit.SECONDS)) {
        try {
            // 获取锁成功
        } finally {
            lock.unlock();
        }
    } else {
        // 超时未获取锁
    }
  6. 条件变量(Condition)
    ReentrantLock 提供了 Condition 对象,用于实现线程间的等待/通知机制,类似于 Object.wait()Object.notify(),但更加灵活。

    使用 lock.newCondition() 方法创建 Condition 对象,线程可以调用 await() 方法等待,调用 signal()signalAll() 方法唤醒等待线程。

    java 复制代码
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ConditionExample {
        private final ReentrantLock lock = new ReentrantLock();
        private final Condition condition = lock.newCondition();
    
        public void waitForSignal() throws InterruptedException {
            lock.lock();
            try {
                condition.await(); // 等待信号
            } finally {
                lock.unlock();
            }
        }
    
        public void sendSignal() {
            lock.lock();
            try {
                condition.signal(); // 发送信号
            } finally {
                lock.unlock();
            }
        }
    }

5.4 ReentrantLock 的优缺点

优点

  1. 更丰富的功能:支持公平锁、尝试锁、中断锁获取、条件变量等功能,灵活性远高于 synchronized
  2. 更精细的控制:显式加锁和解锁,能控制锁的范围和逻辑,更适合复杂场景。
  3. 高性能:在高并发场景下,通过非公平锁、减少线程阻塞切换的开销,相比 synchronized 性能更优。

缺点

  1. 使用复杂:需要显式加锁和解锁,容易因为代码遗漏导致锁未释放。
  2. 易产生死锁:显式加锁机制要求开发者自行管理,若未小心处理容易出现死锁问题。
  3. 不如 synchronized 简洁:对于简单的同步需求,synchronized 更适合。

6 ReentrantLock 和 synchronized 的对比

特性 ReentrantLock synchronized
加锁方式 显式加锁,需要手动调用 lock()unlock() 隐式加锁,无需手动释放
是否支持条件变量 支持,通过 Condition 灵活实现 支持,但只能使用 wait()notify()
中断响应 支持,通过 lockInterruptibly() 响应中断 不支持
公平性 支持公平锁和非公平锁,默认非公平 不支持,锁是非公平的
性能优化 性能更高,适合高并发 JDK 1.6 后性能也有优化
适用场景 适合复杂同步场景,特别是需要精细控制的场景 适合简单的同步需求

7 Semaphore 和 AtomicInteger

1 Semaphore

Semaphorejava.util.concurrent 包中的一个同步工具类,用于控制对共享资源的并发访问数量。它类似于一个计数器,可以通过许可证(permits)的机制限制访问线程的数量。它常用于实现流量控制、资源池管理等场景。在多线程环境下,如果多个线程需要访问有限数量的资源(如数据库连接、文件句柄),使用 Semaphore 可以有效限制并发线程的数量,避免资源耗尽或竞争问题。

2 Semaphore用法

Semaphore 通过 acquire()release() 方法实现许可证的获取和释放。常见用法如下:

java 复制代码
import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    private static final Semaphore semaphore = new Semaphore(3); // 限制最多 3 个线程并发

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    semaphore.acquire(); // 获取许可证
                    System.out.println(Thread.currentThread().getName() + " 获取许可证");
                    Thread.sleep(2000); // 模拟任务执行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release(); // 释放许可证
                    System.out.println(Thread.currentThread().getName() + " 释放许可证");
                }
            }).start();
        }
    }
}
  1. 许可数量 :构造时指定许可证数量,new Semaphore(int permits)

  2. 公平性 :支持公平和非公平模式,默认非公平锁。

    java 复制代码
    Semaphore fairSemaphore = new Semaphore(3, true); // 公平模式
  3. 阻塞和非阻塞

    • acquire():阻塞获取许可证。
    • tryAcquire():尝试获取许可证,失败时立即返回。
  4. 动态调整 :可以通过 reducePermits(int reduction) 方法动态减少许可证数量。

3 Semaphore应用场景

  • 资源池管理:限制线程访问资源池的数量,例如数据库连接池。
  • 限流控制:对系统接口的并发访问量进行限制。
  • 多线程任务分发:协调多线程执行任务的数量。

8 AtomicInteger

AtomicIntegerjava.util.concurrent.atomic 包中的类,用于对整数值进行原子操作。它基于底层的 CAS(Compare-And-Swap)机制,实现了线程安全的整数增减操作,且性能高于传统的锁机制。在多线程环境下,使用普通的 int 类型操作(如 count++)不是线程安全的,因为 count++ 并非原子操作(包括读取、计算和写回三个步骤)。而 AtomicInteger 提供了线程安全的原子性操作,避免了锁的开销。
AtomicInteger常见使用方式:

java 复制代码
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerExample {
    private static final AtomicInteger counter = new AtomicInteger(0);

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                int value = counter.incrementAndGet(); // 原子递增
                System.out.println(Thread.currentThread().getName() + " 值:" + value);
            }).start();
        }
    }
}

** AtomicInteger 的常用方法**

  1. 增减操作

    • incrementAndGet():先递增再返回值。
    • getAndIncrement():先返回值再递增。
    • decrementAndGet()getAndDecrement():类似,用于递减。
  2. 设置和获取值

    • get():获取当前值。
    • set(int newValue):设置为指定值。
    • lazySet(int newValue):最终会设置值,但可能有延迟。
  3. 比较并交换(CAS)

    • compareAndSet(int expect, int update):如果当前值等于 expect,则将值更新为 update
  4. 加减指定值

    • addAndGet(int delta):加上指定值后返回新值。
    • getAndAdd(int delta):返回当前值后再加上指定值。

AtomicInteger 的特性

  • 高性能:基于 CAS 操作,无需加锁,开销小。
  • 线程安全:所有方法都保证了原子性,适合并发环境。
  • 无阻塞算法 :相比 synchronized,性能更优。

AtomicInteger 的应用场景

  • 计数器:用于统计并发环境中的操作次数。
  • 唯一 ID 生成器:生成线程安全的唯一标识符。
  • 限流控制:控制操作次数或速率。

9 读写锁

ReadWriteLockjava.util.concurrent.locks 包中的一个接口,用于实现多线程环境下的读写锁机制。读写锁通过区分读操作和写操作,允许多个线程同时读取共享资源,从而提高并发性;而对于写操作,则必须在没有其他线程进行读或写操作时才允许进行。这种机制有助于优化资源访问,特别是当读操作远多于写操作时,可以显著提升性能。

  • 读锁 (Read Lock):允许多个线程同时获取读锁,当一个线程持有读锁时,其他线程仍然可以获取读锁,但是不能获取写锁。
  • 写锁 (Write Lock):写锁是独占的,只有一个线程可以获取写锁,并且在写锁持有期间,其他线程不能获取读锁或写锁。

在并发编程中,读操作通常比写操作频繁,尤其是在共享资源读取多于写入的场景中。如果使用普通的互斥锁(例如 synchronizedReentrantLock),每次写操作时都会阻塞所有读操作,并且写操作本身也会受到其他写操作的阻塞。这会导致在读操作频繁的情况下,性能下降。

通过读写锁机制:

  • 读锁:允许多个线程并行读取,提升读取操作的并发度。
  • 写锁:保证写操作的独占性,确保数据一致性。

这种机制特别适用于"读多写少"的场景,例如缓存系统、数据库等。

1 读写锁的使用 (ReadWriteLock 接口)

Java 提供了 ReadWriteLock 接口以及其实现类 ReentrantReadWriteLockReentrantReadWriteLock 是最常用的读写锁实现,它提供了读锁和写锁的功能。使用时,通过 readLock()writeLock() 获取相应的锁。

示例代码:

java 复制代码
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockExample {
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private int sharedData = 0;

    // 读操作
    public int read() {
        lock.readLock().lock();  // 获取读锁
        try {
            return sharedData;  // 读取共享数据
        } finally {
            lock.readLock().unlock();  // 释放读锁
        }
    }

    // 写操作
    public void write(int value) {
        lock.writeLock().lock();  // 获取写锁
        try {
            sharedData = value;  // 修改共享数据
        } finally {
            lock.writeLock().unlock();  // 释放写锁
        }
    }

    public static void main(String[] args) {
        ReadWriteLockExample example = new ReadWriteLockExample();

        // 启动多个读线程
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                System.out.println("读线程: " + example.read());
            }).start();
        }

        // 启动写线程
        new Thread(() -> {
            example.write(42);
            System.out.println("写线程: 数据已更新");
        }).start();
    }
}

代码解释:

  • lock.readLock().lock():获取读锁,允许多个线程同时读取数据。
  • lock.writeLock().lock():获取写锁,写锁是独占的,保证写操作期间没有其他线程进行读或写。

2 读写锁的特性

  • 共享读锁:多个线程可以同时获取读锁,并行读取资源。

  • 独占写锁:只有一个线程可以获取写锁,其他线程的读写操作会被阻塞,直到写操作完成。

  • 优先级 :Java 默认的 ReentrantReadWriteLock非公平锁 ,即线程获取锁的顺序并不一定严格按照请求的顺序。如果需要保证公平性,可以通过构造函数传递 true 来创建一个公平锁:

    java 复制代码
    ReadWriteLock fairLock = new ReentrantReadWriteLock(true); // 公平锁

典型使用场景:

  • 缓存:多个线程频繁读取缓存数据,但缓存内容的更新较少。
  • 数据库查询系统:多个线程可以并发执行读操作,但更新(写操作)需要互斥。

3 读写锁的性能

  • 读锁并发性好:允许多个线程同时获取读锁,只要没有线程持有写锁。
  • 写锁独占性强:写锁是独占的,能够保证数据的一致性,但会阻塞所有的读和写操作,性能可能会受到影响,特别是在写操作频繁的情况下。
  • 公平性和非公平性 :默认情况下,ReentrantReadWriteLock 是非公平锁,在高并发场景下,非公平锁能够提供较好的性能。但如果需要避免线程饥饿,可以使用公平锁。

10 锁优化的几种方法

在并发编程中,锁优化是提高系统性能和减少锁竞争的重要手段。常见的锁优化方法包括锁的细化、锁的合并、锁的升级、使用非阻塞算法等。

1. 锁细化 (Lock Splitting)

锁细化是指将一个大范围的锁拆分成多个小范围的锁,每个小范围只控制特定的资源或操作,从而减少锁竞争。当一个锁保护了多个操作或多个资源时,可以考虑将它们分开,使每个操作或资源都有自己的锁。

2. 锁合并 (Lock Coarsening)

锁合并是将多个连续的锁操作合并为一个锁操作,以减少锁的频繁获取和释放,降低锁的开销。减少锁的申请和释放次数,降低上下文切换的成本,提升性能。当多个操作涉及同一个共享资源时,避免为每个操作单独加锁,而是将这些操作合并到一个临界区内,减少锁的获取和释放。

3. 锁升级 (Lock Upgrade)

锁升级是指在锁的获取过程中,从一个较低级别的锁(如读锁)逐步升级到一个更高级别的锁(如写锁)。例如,ReadWriteLock 中可以从读锁升级为写锁。例如,在数据库查询系统中,如果大多数操作是读取数据,可以先获取读锁,只有在需要修改数据时再升级为写锁。

4. 锁消除 (Lock Elision)

锁消除是指在编译或运行时,自动识别某些锁操作实际上不需要加锁,从而去除这些锁的开销。通常由 JVM 或编译器自动优化。如果某个共享资源的访问完全在单个线程中进行,JVM 可以优化掉这些不必要的同步。

5. 偏向锁 (Biased Locking)

偏向锁是一种优化机制,旨在减少无竞争情况下获取锁的开销。它会将锁偏向于第一个获取该锁的线程,避免后续线程的锁竞争。

在没有竞争的情况下,避免了锁的轻量级同步操作,从而减少了获取锁的开销。

6. 锁自由和无锁编程 (Lock-Free and Wait-Free)

锁自由和无锁编程是通过无锁算法(如 CAS 操作)来实现线程安全,而不依赖于传统的锁机制。这种方式避免了锁竞争和上下文切换,提升了并发性能。

  • 锁自由:指在执行过程中,至少有一个线程能在有限的时间内完成操作。
  • 无锁编程:指在执行过程中,每个线程都能在有限的时间内完成操作。
相关推荐
越甲八千2 分钟前
深入理解STL list erase
开发语言·c++·list
sukalot12 分钟前
windows C#-命名实参和可选实参(上)
开发语言·c#
潜洋13 分钟前
Spring Boot教程之三十二:自定义 Jackson ObjectMapper
java·spring boot
KKTT0118 分钟前
Mybatis——(2)
java·spring·mybatis
小旺仔爱代码21 分钟前
IDEA中使用Git
java·ide·intellij-idea
精神病不行计算机不上班22 分钟前
[C++]类的继承
开发语言·c++
shaoweijava22 分钟前
汽车服务管理系统(源码+数据库+报告)
java·数据库·spring boot·mysql
人类群星闪耀时23 分钟前
使用Python实现天文数据分析:探索宇宙的奥秘
开发语言·python·数据分析
Q_192849990632 分钟前
基于Spring Boot的小区车辆管理系统
java·spring boot·后端
Mr. zhihao37 分钟前
Spring Boot 条件注解:@ConditionalOnProperty 完全解析
java·spring boot·后端