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

Java中的锁

这里举例6种

悲观锁和乐观锁是两种不同的并发控制策略,用于解决多线程或多进程环境下对共享资源访问时可能出现的数据不一致问题。下面分别介绍它们的概念、实现方式以及代码示例。

悲观锁

概念

悲观锁假设在整个数据处理过程中,会有其他线程或进程来修改数据,因此在操作数据之前会先对数据进行加锁,确保在自己操作期间其他线程无法访问和修改该数据,直到操作完成并释放锁。这种锁的策略比较“悲观”,常用于写操作频繁的场景。

实现方式
  • 数据库层面:在数据库中,可以使用 SELECT ... FOR UPDATE 语句来实现悲观锁。例如在 MySQL 中,当执行该语句时,会对查询结果集中的记录加行级锁,其他事务想要对这些记录进行写操作时会被阻塞,直到当前事务提交或回滚释放锁。
  • Java 层面:Java 提供了 synchronized 关键字和 ReentrantLock 类来实现悲观锁。synchronized 关键字可以修饰方法或代码块,确保同一时间只有一个线程可以执行被修饰的方法或代码块;ReentrantLock 是一个可重入的互斥锁,通过 lock() 方法加锁,unlock() 方法释放锁。
Java 代码示例
import java.util.concurrent.locks.ReentrantLock;

// 使用 synchronized 关键字实现悲观锁
class SynchronizedCounter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

// 使用 ReentrantLock 实现悲观锁
class ReentrantLockCounter {
    private int count = 0;
    private final ReentrantLock lock = new ReentrantLock();

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    public int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
}

public class PessimisticLockExample {
    public static void main(String[] args) throws InterruptedException {
        // 使用 synchronized 关键字的计数器
        SynchronizedCounter syncCounter = new SynchronizedCounter();
        // 使用 ReentrantLock 的计数器
        ReentrantLockCounter reentrantCounter = new ReentrantLockCounter();

        // 创建线程进行计数操作
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                syncCounter.increment();
                reentrantCounter.increment();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                syncCounter.increment();
                reentrantCounter.increment();
            }
        });

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

        t1.join();
        t2.join();

        System.out.println("Synchronized Counter: " + syncCounter.getCount());
        System.out.println("ReentrantLock Counter: " + reentrantCounter.getCount());
    }
}

乐观锁

概念

乐观锁假设在大多数情况下,其他线程或进程不会同时修改数据,因此在操作数据时不会先加锁,而是在更新数据时检查数据是否被其他线程修改过。如果数据没有被修改,则进行更新操作;如果数据已经被修改,则根据具体情况进行重试或放弃操作。这种锁的策略比较“乐观”,常用于读操作频繁的场景。

实现方式
  • 版本号机制:在数据库表中添加一个版本号字段,每次更新数据时,将版本号加 1。在更新数据时,先查询数据的当前版本号,然后在更新语句中添加版本号的条件,如果版本号与查询时一致,则更新数据并将版本号加 1;如果版本号不一致,则说明数据已经被其他线程修改过,更新失败。
  • CAS(Compare-And-Swap)算法:CAS 是一种无锁算法,它包含三个操作数:内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相等,则将该位置的值更新为新值;否则,不进行更新操作。Java 中的 Atomic 系列类(如 AtomicIntegerAtomicLong 等)就是基于 CAS 算法实现的。
Java 代码示例
import java.util.concurrent.atomic.AtomicInteger;

class OptimisticCounter {
    private AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        // 使用 CAS 操作进行自增
        count.incrementAndGet();
    }

    public int getCount() {
        return count.get();
    }
}

public class OptimisticLockExample {
    public static void main(String[] args) throws InterruptedException {
        OptimisticCounter counter = new OptimisticCounter();

        // 创建线程进行计数操作
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

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

        t1.join();
        t2.join();

        System.out.println("Optimistic Counter: " + counter.getCount());
    }
}

总结

悲观锁和乐观锁各有优缺点,悲观锁可以保证数据的强一致性,但会降低并发性能;乐观锁在并发性能上表现较好,但可能会导致更新失败,需要进行重试操作。在实际应用中,需要根据具体的业务场景选择合适的锁策略。

其他锁

1. 读写锁(ReentrantReadWriteLock

特点
  • 允许多个线程同时进行读操作,但写操作时会独占锁,即同一时间只能有一个线程进行写操作,且写操作时其他读操作也会被阻塞。这种锁适用于读多写少的场景,能显著提升并发性能。
  • 读写锁分为读锁和写锁,读锁是共享锁,写锁是排他锁。
示例代码
import java.util.concurrent.locks.ReentrantReadWriteLock;

class ReadWriteLockDemo {
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
    private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
    private int data = 0;

    public void readData() {
        readLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " 读取数据: " + data);
        } finally {
            readLock.unlock();
        }
    }

    public void writeData(int newData) {
        writeLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " 写入数据: " + newData);
            data = newData;
        } finally {
            writeLock.unlock();
        }
    }
}

public class ReadWriteLockExample {
    public static void main(String[] args) {
        ReadWriteLockDemo demo = new ReadWriteLockDemo();

        // 多个读线程
        for (int i = 0; i < 3; i++) {
            new Thread(demo::readData).start();
        }

        // 写线程
        new Thread(() -> demo.writeData(10)).start();
    }
}

在这段代码中,使用了 lambda 表达式和方法引用的语法来创建并启动多个线程。具体解释如下:

new Thread(() -> demo.writeData(10)).start();

new Thread(…):创建一个新的线程。
() -> demo.writeData(10):这是 lambda 表达式,用于表示一个匿名函数。这里表示一个没有参数的函数,该函数会调用 demo 对象的 writeData 方法,并传递参数 10。
.start():调用线程的 start 方法,启动线程执行 writeData 方法。
for (int i = 0; i < 3; i++) { new Thread(demo::readData).start(); }

这是一个 for 循环,循环三次,每次循环会创建一个新线程来执行 readData 方法。
demo::readData:这是方法引用,表示引用 demo 对象的 readData 方法。方法引用是 lambda 表达式的一种简化形式,如果 lambda 表达式只是调用一个已经存在的方法,则可以使用方法引用来代替。
new Thread(…):和上面一样,创建一个新的线程。
.start():启动线程执行 readData 方法。
总结:这段代码创建了一个写线程和三个读线程,通过 lambda 表达式和方法引用来简化线程任务的定义和启动过程。

2. 信号量(Semaphore

特点
  • 用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用公共资源。
  • 可以把它简单理解成一个计数器,在创建 Semaphore 对象时会指定一个初始值,线程访问资源前需要调用 acquire() 方法获取许可,使用完资源后调用 release() 方法释放许可。
示例代码
import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    private static final Semaphore semaphore = new Semaphore(2);

    public static void main(String[] args) {
        for (int i = 0; i < 5; 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();
        }
    }
}

3. 倒计时门闩(CountDownLatch

特点
  • 允许一个或多个线程等待其他线程完成操作。
  • 通过一个计数器来实现,初始化时指定计数器的值,每个线程完成操作后,调用 countDown() 方法使计数器减 1,当计数器为 0 时,等待的线程可以继续执行。
示例代码
import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        int threadCount = 3;
        CountDownLatch latch = new CountDownLatch(threadCount);

        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + " 开始工作");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    latch.countDown();
                    System.out.println(Thread.currentThread().getName() + " 工作完成");
                }
            }).start();
        }

        latch.await();
        System.out.println("所有线程工作完成,主线程继续执行");
    }
}

4. 循环屏障(CyclicBarrier

特点
  • 让一组线程在到达某个屏障点时进行同步,当所有线程都到达屏障点后,这些线程可以继续执行。
  • CountDownLatch 不同的是,CyclicBarrier 可以重复使用,当线程通过屏障后,计数器会重置,可以继续下一轮的同步。
示例代码
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierExample {
    public static void main(String[] args) {
        int threadCount = 3;
        CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> System.out.println("所有线程已到达屏障点"));

        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + " 正在接近屏障点");
                    barrier.await();
                    System.out.println(Thread.currentThread().getName() + " 已通过屏障点");
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

相关文章:

  • 六、敏捷开发工具:项目管理工具
  • 常用Webpack Loader汇总介绍
  • 【C++】结构体排序+sort(),cmp()参数写法口诀
  • 【时时三省】(C语言基础)三种基本结构和改进的流程图
  • Day01 【苍穹外卖】环境搭建与前后端联调
  • 【Java】泛型与集合篇(二)
  • cesium视频投影
  • 【核心算法篇三】《DeepSeek强化学习:Atari游戏训练框架解析》
  • 使用 Docker 部署 Apache Spark 集群教程
  • IDEA——Mac版快捷键
  • 如何使用 MTG2000 和 FreeSWITCH 通过 SIP Trunk 搭建呼叫中心
  • Jetpack Compose系列教程之(16)——Compose生命周期及副作用函数
  • 玩客云 IP查找
  • 【C语言】第四期——循环语句
  • MySQL数据迁移工具
  • DeepSeek预测25考研分数线
  • Dubbo
  • LeetCode1287
  • 记一次Ngnix配置
  • 开源项目的认识理解
  • 港理大研究揭示:塑胶废物潜藏微生物群落或引发生态危机
  • 上海一中院一审公开开庭审理被告人胡欣受贿案
  • 中国电信财务部总经理周响华调任华润集团总会计师
  • 外交部:中方和欧洲议会决定同步全面取消对相互交往的限制
  • 美权威人士批“特朗普对进口电影征关税”:将杀死美电影产业
  • 2025五一档电影票房破6亿