在计算机科学中,锁是一种同步机制,用于控制多个线程或进程对共享资源(如数据)的访问,以防止数据竞态和确保数据一致性。在Java中,锁的概念主要体现在多线程环境下,用于解决并发编程中的同步问题。

Java中有几种不同类型的锁:

1、内部锁 (Intrinsic Locks)或监视器锁 (Monitor Locks):

  • 这些锁通过使用synchronized关键字来获得。每个Java对象都有一个内部锁,当一个线程进入一个实例方法或块时,它会自动获取该对象的锁。

  • 如果方法是static的,则它取得的是类对象对应的锁。

  • 示例代码:

public class Counter {    private int count = 0;        // 使用内部锁的同步方法    public synchronized void increment() {        count++;    }        // 使用内部锁的同步代码块    public void decrement() {        synchronized(this) {            count--;        }    }}

2、显式锁 (Explicit Locks):

  • 在java.util.concurrent.locks包中提供了更复杂的锁机制,例如ReentrantLock,允许更灵活的锁管理。

  • ReentrantLock支持重入,意味着已经拥有该锁的线程可以再次获取锁而不会被阻塞。

  • 示例代码:

import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class Counter {    private final Lock lock = new ReentrantLock();    private int count = 0;        public void increment() {        lock.lock();        try {            count++;        } finally {            lock.unlock();        }    }}

3、读/写锁 (Read-Write Locks):

  • 例如ReentrantReadWriteLock,允许多个线程同时读取,但只允许一个线程进行写入。

  • 它包含一对锁:一个读锁和一个写锁,通过分离读和写操作来提高性能。

  • 示例代码:

import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent.locks.ReentrantReadWriteLock;public class SharedDataStructure {    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();    private final Lock readLock = rwLock.readLock();    private final Lock writeLock = rwLock.writeLock();    private int data = 0;        public void writeData(int newData) {        writeLock.lock();        try {            data = newData;        } finally {            writeLock.unlock();        }    }        public int readData() {        readLock.lock();        try {            return data;        } finally {            readLock.unlock();        }    }}

4、乐观锁/Optimistic Locks和悲观锁/Pessimistic Locks

乐观锁(Optimistic Locks)

乐观锁假设多个事务同时对同一条记录进行编辑的几率很低,因此它不会立即锁定数据。相反,当事务提交时,通过比较数据版本来检查在读取数据后是否有其他事务进行了修改。

Java中通常使用版本号或时间戳字段来实现乐观锁。例如,在JPA中,你可以使用@Version注解来标记一个字段作为版本控制:

import javax.persistence.Entity;import javax.persistence.Id;import javax.persistence.Version;@Entitypublic class ExampleEntity {    @Id    private Long id;        @Version    private Integer version;    // getters and setters ...}
当更新这个实体时,JPA提供者会检查数据库中的版本号是否与实体中的版本号匹配,如果不匹配,则抛出OptimisticLockException异常。

悲观锁(Pessimistic Locks)

悲观锁认为冲突是常见的,并且总是假设最坏情况——在访问数据前,它会锁定数据以防止其他事务修改。
在JPA中,你可以通过查询提示来指定悲观锁模式。例如,获取一个排他的悲观锁:
import javax.persistence.LockModeType;import javax.persistence.EntityManager;ExampleEntity entity = entityManager.find(ExampleEntity.class, entityId, LockModeType.PESSIMISTIC_WRITE);

当使用PESSIMISTIC_WRITE时,JPA将使用相应的数据库锁,如SQL的SELECT FOR UPDATE语句,直到当前事务完成后才释放锁。

总结

  • 这些不是Java语言结构,而是基于不同策略实现锁机制的概念。

  • 乐观锁通常用在没有严格同步要求的场景中,并假设冲突很少发生。常见的实现方式有版本号等。

  • 悲观锁假设冲突很常见,因此总是先加锁。数据库事务中的锁机制就是一个例子。

5、条件锁 (Condition Locks):

  • 条件锁允许线程之间有更细粒度的协作。与ReentrantLock一起使用,一个锁可以有一个或多个条件对象。

  • 这些条件对象可以控制线程在某个特定状态下暂停执行(await),直到另一个线程在该条件上发送信号(signal)或广播(signalAll)后才继续执行。

  • 示例代码:

public class BoundedBuffer {    final Lock lock = new ReentrantLock();    final Condition notFull  = lock.newCondition();     final Condition notEmpty = lock.newCondition();     final Object[] items = new Object[100];    int putptr, takeptr, count;    public void put(Object x) throws InterruptedException {        lock.lock();        try {            while (count == items.length)                notFull.await();            items[putptr] = x;            if (++putptr == items.length) putptr = 0;            ++count;            notEmpty.signal();        } finally {            lock.unlock();        }    }    public Object take() throws InterruptedException {        lock.lock();        try {            while (count == 0)                notEmpty.await();            Object x = items[takeptr];            if (++takeptr == items.length) takeptr = 0;            --count;            notFull.signal();            return x;        } finally {            lock.unlock();        }    }}

本篇文章来源于微信公众号: 互联网面试小帮手



微信扫描下方的二维码阅读本文

此作者没有提供个人介绍
最后更新于 2024-05-04