各位 Java 后端开发的小伙伴们,大家好!今天咱们来聊聊 Java 中的锁。你可别小瞧这小小的锁,它在并发编程的世界里,可是起着至关重要的作用呢!就好比你家的门锁,保护着家里的安全,Java 中的锁则守护着我们程序的数据安全和并发访问的秩序。
一、为什么需要锁?
在多线程的世界里,多个线程同时访问和修改共享资源时,就可能会出现数据不一致的问题。这就好比几个人同时去抢一个玩具,抢来抢去,玩具可能就坏掉了(数据乱套了)。这时候,锁就派上用场啦,它就像一个 “门卫”,每次只允许一个线程进入访问共享资源,其他线程只能乖乖排队等待。
二、常见的 Java 锁
(一)synchronized 关键字
这可是 Java 中最基础的锁啦,就像家里最普通的那种门锁,简单直接。只要你在方法或者代码块前加上它,就给这段代码上了一把锁。
public class SynchronizedExample {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
在上面的代码中,increment方法被synchronized修饰,这就意味着当一个线程在执行这个方法时,其他线程想要调用这个方法,就只能在门外等着,直到这个线程执行完方法,把锁释放。
(二)ReentrantLock
ReentrantLock是一种可重入的互斥锁,它比synchronized更灵活一些。就好比你家的智能门锁,不仅能锁门,还能有更多的功能。它可以手动控制锁的获取和释放,还支持公平锁和非公平锁模式。
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();
}
}
public int getCount() {
return count;
}
}
在这段代码中,我们通过lock.lock()获取锁,在try块中执行需要同步的代码,最后在finally块中通过lock.unlock()释放锁,确保锁一定会被释放,避免死锁。
(三)ReadWriteLock
读写锁,听名字就知道它是用来处理读和写操作的。它就像一个图书馆的阅览室,允许多个读者同时阅读(多个线程同时读操作),但是当有人要写东西(写操作)时,就不允许其他人读或者写了。这样可以提高并发性能,因为读操作不会修改数据,所以可以同时进行。
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExample {
private int data = 0;
private ReadWriteLock lock = new ReentrantReadWriteLock();
public void read() {
lock.readLock().lock();
try {
System.out.println("Reading data: " + data);
} finally {
lock.readLock().unlock();
}
}
public void write(int newData) {
lock.writeLock().lock();
try {
data = newData;
System.out.println("Writing data: " + data);
} finally {
lock.writeLock().unlock();
}
}
}
在这个例子中,read方法使用读锁,write方法使用写锁,很好地控制了读写的并发访问。
(四)StampedLock
StampedLock是 Java 8 引入的一种新型锁,它比ReadWriteLock更灵活。它就像一个带有时间戳的通行证,在获取锁的时候会返回一个时间戳,在释放锁和验证锁的时候都要用到这个时间戳。它支持三种模式:读模式、写模式和乐观读模式。乐观读模式就像是你去超市买东西,先假设你不会和别人冲突,直接去拿东西,等真正要结账(修改数据)的时候,再检查一下有没有冲突。
import java.util.concurrent.locks.StampedLock;
public class StampedLockExample {
private double x, y;
private final StampedLock sl = new StampedLock();
void move(double deltaX, double deltaY) {
long stamp = sl.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp);
}
}
double distanceFromOrigin() {
long stamp = sl.tryOptimisticRead();
double currentX = x, currentY = y;
if (!sl.validate(stamp)) {
stamp = sl.readLock();
try {
currentX = x;
currentY = y;
} finally {
sl.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
}
在上面的代码中,move方法使用写锁,distanceFromOrigin方法使用乐观读模式,如果验证失败再转为读锁。
三、总结
Java 中的锁种类繁多,每一种都有它自己的特点和适用场景。就像我们生活中有各种各样的锁,不同的地方要用不同的锁来保护。在实际开发中,我们要根据具体的业务需求,选择合适的锁,这样才能让我们的程序既安全又高效。希望今天的分享能让大家对 Java 中的锁有更深入的了解,在并发编程的道路上少踩一些坑!