主页 > 游戏开发  > 

Java中的锁

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 系列类(如 AtomicInteger、AtomicLong 等)就是基于 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(); } } }
标签:

Java中的锁由讯客互联游戏开发栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“Java中的锁