一文搞懂各种锁的使用姿势

444 阅读4分钟

各位 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 中的锁有更深入的了解,在并发编程的道路上少踩一些坑!