java中的各类锁,你了解多少?

在并发编程中,锁机制是确保数据一致性和线程安全的重要手段。不同的锁类型和优化技术各有特点,适用于不同的场景。下面是对你提到的各种锁类型的详细解释:

1. 乐观锁 vs 悲观锁

乐观锁(Optimistic Locking)
  • 基本思想:

    • 假设多个事务可以并发执行且不会相互影响。
    • 在提交更新之前,检查数据是否被其他事务修改过。
  • 实现方式:

    • 使用版本号(Version Number)或时间戳(Timestamp)来跟踪数据的变化。
    • 在更新数据时,先检查版本号或时间戳是否与预期一致,如果不一致则重试或报错。
  • 优点:

    • 减少了锁竞争,提高了并发性能。
    • 适用于读多写少的场景。
  • 缺点:

    • 如果冲突频繁,可能导致大量重试,降低性能。
    • 需要额外的版本号字段或时间戳字段。
  • 示例代码:

java

public class OptimisticLockExample {
    private int version = 0;
    private int value;

    public synchronized boolean update(int newValue) {
        if (version != expectedVersion) {
            return false; // 数据已被其他事务修改,重试
        }
        value = newValue;
        version++;
        return true;
    }
}
悲观锁(Pessimistic Locking)
  • 基本思想:

    • 假设多个事务会相互影响,因此在操作数据前先加锁。
    • 其他事务必须等待锁释放后才能进行操作。
  • 实现方式:

    • 使用数据库中的行级锁、表级锁等。
    • 在 Java 中常用 synchronized 关键字或 ReentrantLock 实现。
  • 优点:

    • 简单可靠,避免了脏读、不可重复读等问题。
    • 适用于写多读少的场景。
  • 缺点:

    • 增加了锁竞争,降低了并发性能。
    • 可能导致死锁问题。
  • 示例代码:

java

public class PessimisticLockExample {
    private int value;

    public synchronized void update(int newValue) {
        value = newValue;
    }

    public synchronized int getValue() {
        return value;
    }
}

2. 独占锁 vs 共享锁

独占锁(Exclusive Lock)
  • 基本思想:

    • 一次只有一个线程可以持有锁。
    • 其他线程必须等待锁释放后才能获取锁。
  • 实现方式:

    • Java 中常用的独占锁有 synchronized 和 ReentrantLock
  • 优点:

    • 确保同一时刻只有一个线程访问资源,防止数据不一致。
  • 缺点:

    • 增加了锁竞争,降低了并发性能。
  • 示例代码:

java

public class ExclusiveLockExample {
    private int value;

    public synchronized void update(int newValue) {
        value = newValue;
    }

    public synchronized int getValue() {
        return value;
    }
}
共享锁(Shared Lock)
  • 基本思想:

    • 多个线程可以同时持有锁。
    • 写操作需要独占锁,读操作可以共享锁。
  • 实现方式:

    • Java 中常用的共享锁有 ReadWriteLock 接口及其实现类 ReentrantReadWriteLock
  • 优点:

    • 提高了读操作的并发性能。
    • 适用于读多写少的场景。
  • 缺点:

    • 写操作会导致所有读操作等待,可能降低性能。
  • 示例代码:

java

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class SharedLockExample {
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private int value;

    public void update(int newValue) {
        lock.writeLock().lock();
        try {
            value = newValue;
        } finally {
            lock.writeLock().unlock();
        }
    }

    public int getValue() {
        lock.readLock().lock();
        try {
            return value;
        } finally {
            lock.readLock().unlock();
        }
    }
}

3. 互斥锁 vs 读写锁

互斥锁(Mutex Lock)
  • 基本思想:

    • 同一时刻只有一个线程可以持有锁。
    • 类似于独占锁。
  • 实现方式:

    • Java 中常用的互斥锁有 synchronized 和 ReentrantLock
  • 优点:

    • 确保同一时刻只有一个线程访问资源,防止数据不一致。
  • 缺点:

    • 增加了锁竞争,降低了并发性能。
  • 示例代码:

java

public class MutexLockExample {
    private int value;

    public synchronized void update(int newValue) {
        value = newValue;
    }

    public synchronized int getValue() {
        return value;
    }
}
读写锁(Read-Write Lock)
  • 基本思想:

    • 多个线程可以同时持有读锁。
    • 写操作需要独占锁,读操作可以共享锁。
  • 实现方式:

    • Java 中常用的读写锁有 ReadWriteLock 接口及其实现类 ReentrantReadWriteLock
  • 优点:

    • 提高了读操作的并发性能。
    • 适用于读多写少的场景。
  • 缺点:

    • 写操作会导致所有读操作等待,可能降低性能。
  • 示例代码:

java

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

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

    public void update(int newValue) {
        lock.writeLock().lock();
        try {
            value = newValue;
        } finally {
            lock.writeLock().unlock();
        }
    }

    public int getValue() {
        lock.readLock().lock();
        try {
            return value;
        } finally {
            lock.readLock().unlock();
        }
    }
}

4. 公平锁 vs 非公平锁

公平锁(Fair Lock)
  • 基本思想:

    • 线程按照请求锁的顺序获得锁。
    • 新来的线程会被放入队列尾部等待。
  • 实现方式:

    • Java 中可以通过 ReentrantLock(true) 创建公平锁。
  • 优点:

    • 避免饥饿现象,每个线程都有机会获得锁。
  • 缺点:

    • 增加了上下文切换开销,可能降低性能。
  • 示例代码:

java

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class FairLockExample {
    private final Lock lock = new ReentrantLock(true);
    private int value;

    public void update(int newValue) {
        lock.lock();
        try {
            value = newValue;
        } finally {
            lock.unlock();
        }
    }

    public int getValue() {
        lock.lock();
        try {
            return value;
        } finally {
            lock.unlock();
        }
    }
}
非公平锁(Non-Fair Lock)
  • 基本思想:

    • 线程尝试立即获取锁,如果失败则加入队列。
    • 新来的线程有可能插队获得锁。
  • 实现方式:

    • Java 中默认的 ReentrantLock 是非公平锁,也可以通过 ReentrantLock(false) 明确指定。
  • 优点:

    • 减少了上下文切换开销,提高吞吐量。
    • 更高的性能。
  • 缺点:

    • 可能导致某些线程长期无法获得锁,引发饥饿现象。
  • 示例代码:

java

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class NonFairLockExample {
    private final Lock lock = new ReentrantLock(false);
    private int value;

    public void update(int newValue) {
        lock.lock();
        try {
            value = newValue;
        } finally {
            lock.unlock();
        }
    }

    public int getValue() {
        lock.lock();
        try {
            return value;
        } finally {
            lock.unlock();
        }
    }
}

5. 可重入锁(Reentrant Lock)

  • 基本思想:

    • 同一个线程可以多次获取同一个锁,而不会发生死锁。
    • 每次获取锁计数器加1,每次释放锁计数器减1,当计数器为0时锁完全释放。
  • 实现方式:

    • Java 中常用的可重入锁是 ReentrantLock
  • 优点:

    • 解决了普通锁不能重复获取的问题。
    • 支持公平锁和非公平锁两种模式。
  • 缺点:

    • 相对复杂的实现,增加了开发难度。
  • 示例代码:

java 

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private final Lock lock = new ReentrantLock();
    private int value;

    public void nestedMethod() {
        lock.lock();
        try {
            innerMethod();
        } finally {
            lock.unlock();
        }
    }

    private void innerMethod() {
        lock.lock();
        try {
            value++;
        } finally {
            lock.unlock();
        }
    }
}

6. 自旋锁(Spin Lock)

  • 基本思想:

    • 当线程尝试获取锁但失败时,不是立即阻塞而是持续循环(自旋)等待锁的释放。
    • 适用于锁占用时间短的情况。
  • 实现方式:

    • Java 中没有直接提供自旋锁,但可以通过 AtomicInteger 或 Unsafe 类实现。
  • 优点:

    • 减少了线程上下文切换的开销。
    • 提高了性能。
  • 缺点:

    • 占用 CPU 资源,可能导致 CPU 过热。
    • 不适合锁占用时间长的情况。
  • 示例代码:

java

 

import java.util.concurrent.atomic.AtomicBoolean;

public class SpinLockExample {
    private AtomicBoolean locked = new AtomicBoolean(false);

    public void lock() {
        while (!locked.compareAndSet(false, true)) {
            // 自旋等待
        }
    }

    public void unlock() {
        locked.set(false);
    }

    private int value;

    public void update(int newValue) {
        lock();
        try {
            value = newValue;
        } finally {
            unlock();
        }
    }

    public int getValue() {
        lock();
        try {
            return value;
        } finally {
            unlock();
        }
    }
}

7. 分段锁(Segmented Lock)

  • 基本思想:

    • 将数据分成多个段(Segments),每个段由独立的锁保护。
    • 多个线程可以同时操作不同段的数据,减少了锁竞争。
  • 实现方式:

    • Java 中的 ConcurrentHashMap 使用了分段锁机制。
  • 优点:

    • 提高了并发性能。
    • 适用于大数据量的并发访问场景。
  • 缺点:

    • 实现复杂,增加开发难度。
  • 示例代码:

java

 

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class SegmentedLockExample {
    private final Map<String, Integer> map = new ConcurrentHashMap<>();

    public void put(String key, Integer value) {
        map.put(key, value);
    }

    public Integer get(String key) {
        return map.get(key);
    }
}

8. 锁升级(无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁)

  • 无锁(No Locking):

    • 没有任何锁机制,适用于数据几乎不变化的场景。
    • 例如:使用 CAS 操作(Compare And Swap)。
  • 偏向锁(Biased Locking):

    • 假设锁总是被同一个线程持有。
    • 第一次获取锁时记录线程 ID,后续该线程再次获取锁时无需 CAS 操作。
    • 适用于大多数情况下锁只被一个线程持有的场景。
  • 轻量级锁(Lightweight Locking):

    • 当两个线程交替持有锁时,使用轻量级锁。
    • 通过 CAS 操作将对象头的 Mark Word 替换为指向线程栈帧的指针。
    • 适用于锁竞争不是很激烈的场景。
  • 重量级锁(Heavyweight Locking):

    • 当锁竞争激烈时,升级为重量级锁。
    • 涉及操作系统级别的线程挂起和恢复,性能较低。
    • 适用于锁竞争非常激烈的场景。
  • 锁升级过程:

    • 无锁 -> 偏向锁: 第一个线程获取锁时设置偏向位。
    • 偏向锁 -> 轻量级锁: 发生锁竞争时撤销偏向锁,进入轻量级锁状态。
    • 轻量级锁 -> 重量级锁: 多次 CAS 失败后,膨胀为重量级锁。
  • 示例代码:

java

 

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockUpgradeExample {
    private final Lock lock = new ReentrantLock();

    public void biasedLockExample() {
        lock.lock();
        try {
            // 偏向锁
            System.out.println("Biased Lock");
        } finally {
            lock.unlock();
        }
    }

    public void lightweightLockExample() {
        Thread t1 = new Thread(() -> {
            lock.lock();
            try {
                // 轻量级锁
                System.out.println("Thread 1 Lightweight Lock");
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

        Thread t2 = new Thread(() -> {
            lock.lock();
            try {
                // 轻量级锁
                System.out.println("Thread 2 Lightweight Lock");
            } finally {
                lock.unlock();
            }
        });

        t1.start();
        t2.start();
    }

    public void heavyweightLockExample() {
        Thread t1 = new Thread(() -> {
            lock.lock();
            try {
                // 重量级锁
                System.out.println("Thread 1 Heavyweight Lock");
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

        Thread t2 = new Thread(() -> {
            lock.lock();
            try {
                // 重量级锁
                System.out.println("Thread 2 Heavyweight Lock");
            } finally {
                lock.unlock();
            }
        });

        t1.start();
        t2.start();
    }

    public static void main(String[] args) throws InterruptedException {
        LockUpgradeExample example = new LockUpgradeExample();
        example.biasedLockExample();
        example.lightweightLockExample();
        example.heavyweightLockExample();
        t1.join();
        t2.join();
    }
}

9. 锁优化技术(锁粗化、锁消除)

锁粗化(Lock Coarsening)
  • 基本思想:

    • 将连续的加锁解锁操作合并为一个大的加锁解锁操作。
    • 减少锁的操作次数,提高性能。
  • 实现方式:

    • JVM 自动进行锁粗化优化。
  • 优点:

    • 减少了锁的操作次数,提高了性能。
  • 缺点:

    • 可能增加锁的持有时间,影响其他线程的并发性。
  • 示例代码:

java

 

public class LockCoarseningExample {
    private final Object lock = new Object();
    private int value;

    public void updateMultipleTimes() {
        synchronized (lock) {
            value++;
            value--;
            value += 10;
            value -= 5;
        }
    }
}
锁消除(Lock Elimination)
  • 基本思想:

    • 编译器检测到某段代码中使用的锁对象不会被其他线程访问时,自动移除锁。
    • 减少了不必要的锁操作。
  • 实现方式:

    • JVM 自动进行锁消除优化。
  • 优点:

    • 完全消除了锁的开销,提高了性能。
  • 缺点:

    • 需要编译器能够准确判断锁对象的安全性。
  • 示例代码:

 java

public class LockEliminationExample {
    private int value;

    public void update() {
        StringBuilder sb = new StringBuilder();
        sb.append("Hello");
        sb.append("World");
        value = sb.length();
    }
}
  • 在这个例子中,StringBuilder 对象是局部变量,不会被其他线程访问,因此 JVM 可以对其进行锁消除。

总结

  • 乐观锁 和 悲观锁 主要区别在于对并发冲突的假设,前者适用于读多写少的场景,后者适用于写多读少的场景。
  • 独占锁 和 共享锁 区别在于允许多个线程同时持有锁的程度,前者严格限制,后者允许多个读操作共存。
  • 互斥锁 和 读写锁 的区别类似于独占锁和共享锁,前者严格控制,后者允许部分共享。
  • 公平锁 和 非公平锁 区别在于线程获取锁的顺序,前者按顺序,后者可能插队。
  • 可重入锁 允许同一个线程多次获取锁,解决了普通锁的局限。
  • 自旋锁 适用于锁占用时间短的场景,减少线程阻塞的开销。
  • 分段锁 通过分割数据段,减少锁的竞争。
  • 锁升级 是一种动态调整锁策略的方式,从无锁到重量级锁逐层升级。
  • 锁优化技术 如锁粗化和锁消除,通过减少锁的操作次数和消除不必要的锁操作,提高性能。

这些锁类型和技术各有优劣,选择合适的锁机制对于提升并发系统的性能至关重要。根据具体的业务场景和需求,合理选择和配置锁机制,可以显著改善系统的稳定性和效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

慧香一格

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

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

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

打赏作者

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

抵扣说明:

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

余额充值