文章快速指引
一、java中锁的作用
在Java中,锁(Locks)是一种同步机制,主要用于控制多线程环境下的资源访问,确保数据的一致性和线程安全。锁的主要作用可以概括为以下几个方面:
- 互斥访问
锁能够防止多个线程同时访问共享资源,确保任何时刻只有一个线程能够进入临界区,从而避免数据竞争和不一致状态。 - 原子操作保证
通过锁,可以确保某些关键操作(如修改共享变量)在执行时不会被其他线程中断,保证了这些操作的原子性。 - 线程同步
锁可以用于实现线程间的同步,例如一个线程等待另一个线程完成某个任务后继续执行,这有助于协调多线程程序中的复杂流程。 - 避免死锁和活锁
虽然锁本身可能导致死锁和活锁问题,但合理使用锁机制可以避免这些问题的发生,确保程序的稳定运行。 - 提高并发性能
虽然锁会带来一定的性能开销,但在适当的地方使用锁可以提高并发性能,尤其是在多处理器系统中,合理的锁策略能够有效利用硬件资源。 - 读写锁优化
Java提供了读写锁(ReentrantReadWriteLock),允许多个读取线程同时访问共享资源,但写入操作是排他的。这种机制在读操作远多于写操作的场景下,可以显著提升并发性能。 - 公平性和非公平性
Java中的锁还可以设置为公平锁或非公平锁,公平锁按照线程请求锁的顺序分配锁,而非公平锁则可能让后来的线程先获得锁,提高了锁的获取速度,但牺牲了一定的公平性。
二、synchronized
-
使用场景
在简单的多线程环境中,当需要确保一个共享资源在同一时间只能被一个线程访问时,synchronized关键字非常有用。例如,用于同步对全局变量的访问,或是在银行账户转账、票务系统中确保事务的一致性。 -
代码示例
public class SynchronizedExample { private int count = 0; private Object lock = new Object(); public void increment() { synchronized (lock) { count++; } } }
三、ReentrantLock
-
使用场景
当需要更灵活的锁控制时,比如可中断的锁、公平锁或是需要在锁上附加额外行为时,ReentrantLock是更好的选择。例如,在实现一个线程池或管理一个线程安全的队列时,使用ReentrantLock可以更好地控制锁的行为。 -
代码示例
import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockExample { private int count = 0; private ReentrantLock lock = new ReentrantLock(); public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); } } }
四、Condition
-
使用场景
Condition接口常与ReentrantLock搭配使用,用于更精细的线程间通信,如生产者-消费者模型中,生产者和消费者线程可以基于不同的条件进行等待和唤醒,而不需要整个锁的范围被阻塞。 -
代码示例
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class ConditionExample { private int count = 0; private ReentrantLock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void increment() { lock.lock(); try { count++; condition.signalAll(); } finally { lock.unlock(); } } public void await() throws InterruptedException { lock.lock(); try { while (count == 0) { condition.await(); } // 执行其他操作 } finally { lock.unlock(); } } }
五、StampedLock
-
使用场景
在读操作远远多于写操作的场景中,StampedLock提供了一种高性能的读写锁机制。例如,在数据库查询结果缓存中,大多数操作都是读取,偶尔才会有写入,此时使用StampedLock可以显著提高读取性能。 -
代码示例
import java.util.concurrent.locks.StampedLock; public class StampedLockExample { private int x = 0; private int y = 0; private StampedLock lock = new StampedLock(); public void writeData(int newX, int newY) { long stamp = lock.writeLock(); try { x = newX; y = newY; } finally { lock.unlockWrite(stamp); } } public double readDistanceFromOrigin() { long stamp = lock.tryOptimisticRead(); int currentX = x; int currentY = y; if (!lock.validate(stamp)) { stamp = lock.readLock(); try { currentX = x; currentY = y; } finally { lock.unlockRead(stamp); } } return Math.sqrt(currentX * currentX + currentY * currentY); } }
六、LockSupport
- 使用场景
LockSupport主要用于实现自定义的同步器或更底层的线程同步机制。例如,在设计自定义的信号量或实现更复杂的线程调度算法时,LockSupport提供了必要的原语支持。 - 代码示例
import java.util.concurrent.locks.LockSupport; public class LockSupportExample { public void doSomething() { // 执行前置操作 LockSupport.park(); // 阻塞当前线程 // 执行后置操作 } public void wakeupThread() { // 唤醒阻塞的线程 LockSupport.unpark(thread); } }
七、CountDownLatch
-
使用场景
当一个线程需要等待若干个子线程全部完成某个任务后才能继续执行时,CountDownLatch非常合适。例如,在分布式系统中,主节点可能需要等待所有子节点完成数据处理后才能汇总结果。 -
代码示例
import java.util.concurrent.CountDownLatch; public class CountDownLatchExample { private CountDownLatch latch = new CountDownLatch(3); public void doSomething() throws InterruptedException { // 执行前置操作 latch.countDown(); // 完成任务,计数器减1 latch.await(); // 等待其他线程完成任务 // 执行后置操作 } }