当前位置: 首页 > article >正文

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 类。以下是一个简单的自旋锁实现示例:

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. 修饰实例方法
    为实例方法加锁,锁定的是当前对象实例,确保同一时间只有一个线程可以调用该实例的同步方法。

    public class Counter {
        private int count = 0;
    
        public synchronized void increment() {
            count++;
        }
    }
    

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

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

    public class Counter {
        private static int count = 0;
    
        public static synchronized void increment() {
            count++;
        }
    }
    

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

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

    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. 可重入性:一个线程可以多次获取同一个对象的锁,而不会发生死锁。这是因为每次锁的获取都会增加锁的计数器,释放锁时计数器递减。
    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. 锁粒度要小:避免锁定过大的代码块,减少线程竞争的范围。

    // 不推荐:锁住整个方法
    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 的基本使用方式:

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,直到计数器归零,锁才被真正释放。

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

    • 公平锁:按照线程请求锁的顺序分配锁,先请求先获取,避免线程“饥饿”。
    • 非公平锁:可能会插队,线程在尝试获取锁时直接竞争锁,提高吞吐量,但可能导致某些线程长期得不到锁。
    ReentrantLock fairLock = new ReentrantLock(true); // 公平锁
    ReentrantLock unfairLock = new ReentrantLock(false); // 非公平锁
    
  4. 中断锁获取
    使用 lockInterruptibly() 方法,线程在尝试获取锁时可以响应中断。适合处理线程需要取消或超时的场景。

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

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

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

    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() 方法唤醒等待线程。

    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 的对比

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

7 Semaphore 和 AtomicInteger

1 Semaphore

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

2 Semaphore用法

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

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. 公平性:支持公平和非公平模式,默认非公平锁。
    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常见使用方式:

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() 获取相应的锁。

示例代码:

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 来创建一个公平锁:
    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 操作)来实现线程安全,而不依赖于传统的锁机制。这种方式避免了锁竞争和上下文切换,提升了并发性能。

  • 锁自由:指在执行过程中,至少有一个线程能在有限的时间内完成操作。
  • 无锁编程:指在执行过程中,每个线程都能在有限的时间内完成操作。

http://www.kler.cn/a/442048.html

相关文章:

  • STM32-笔记3-驱动蜂鸣器
  • 警告 torch.nn.utils.weight_norm is deprecate 的参考解决方法
  • Scala 的迭代器
  • 基于遗传优化SVM支持向量机的数据分类算法matlab仿真,SVM通过编程实现,不使用工具箱
  • VMware Workstation Pro 17 与 虚拟机 ——【安装操作】
  • NoSQL数据库介绍与分类
  • 引言和相关工作的区别
  • AOP实现操作日志记录+SQL优化器升级
  • NFT市场回暖:蓝筹项目成为复苏主力,空投潮助推价格上涨
  • Android 13 Aosp SystemServer功能裁剪(PackageManager.hasSystemFeature())
  • Jenkins搭建并与Harbor集成上传镜像
  • 如何查看K8S集群中service和pod定义的网段范围
  • 备战美赛!2025美赛数学建模C题模拟预测!用于大家练手模拟!
  • Python之公共操作篇
  • AirSim 无人机不同视角采集不同场景的图片
  • SEO短视频矩阵系统源码开发概述
  • 域名历史是什么?怎么进行域名历史查询?
  • gorm源码解析(四):事务,预编译
  • Java基础知识(四) -- 面向对象(中)
  • 量化交易实操入门