Java 中的 synchronized 和 Lock 使用指南

在多线程编程中,确保数据的一致性和线程安全是至关重要的。在 Java 中,我们有两种主要的机制来实现线程同步:synchronized 关键字和 Lock 接口。本文将逐步教你如何使用它们,并帮助你理解各自的流程和使用场景。

实现步骤概览

以下是使用 synchronizedLock 的基本步骤:

步骤 操作
1 创建一个共享资源类
2 使用 synchronized 关键字同步方法或代码块
3 创建一个共享资源类
4 使用 ReentrantLock 实现显式锁
5 释放锁并处理异常

步骤详解

1. 创建一个共享资源类

首先,我们需要一个共享资源,假设这是一个简单的计数器类。

public class Counter {
    private int count = 0; // 共享资源

    // 增加计数的方法
    public void increment() {
        count++;
    }

    // 读取计数的方法
    public int getCount() {
        return count;
    }
}

解释: Counter 类中有一个整数 count,它是需要被多个线程访问和修改的共享资源。increment 方法是用于增加 count 的。

2. 使用 synchronized 关键字

接下来,我们使用 synchronized 关键字来保证 increment 方法的线程安全性:

public synchronized void increment() {
    count++; // 增加计数
}

或者使用一个同步代码块:

public void increment() {
    synchronized (this) { // 对当前对象加锁
        count++;
    }
}

解释: 无论是哪种方式,使用 synchronized 可以确保同一时间内只有一个线程可以执行 increment 方法,避免了数据不一致的情况。

3. 创建一个共享资源类(使用 Lock)

如果需要更细粒度的控制,可以使用 Lock 接口。首先要创建一个新的计数器类:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockCounter {
    private int count = 0;
    private Lock lock = new ReentrantLock();  // 初始化锁

    // 增加计数的方法
    public void increment() {
        lock.lock(); // 加锁
        try {
            count++; // 增加计数
        } finally {
            lock.unlock(); // 确保释放锁
        }
    }

    // 读取计数的方法
    public int getCount() {
        return count;
    }
}

解释: 在 LockCounter 类中,我们使用 ReentrantLock 来实现锁的机制。在 increment 方法中,我们在增加 count 前使用 lock() 方法加锁,并在 finally 块中确保 unlock() 方法被调用,无论 try 中是否抛出异常。

4. 释放锁并处理异常

锁一定要在使用后释放,这样才能避免死锁的发生。上述代码中的 finally 块确保了锁的释放。此外,我们可以处理不同类型的异常,以增强代码的健壮性。

5. 实现一个简单的测试

创建一个测试类来验证我们的 CounterLockCounter 的功能:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        LockCounter lockCounter = new LockCounter();

        // 启动线程测试 synchronized
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        // 启动线程测试 Lock
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                lockCounter.increment();
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("Counter: " + counter.getCount());
        System.out.println("LockCounter: " + lockCounter.getCount());
    }
}

解释: 在这个测试类中,我们创建了两个线程分别测试 CounterLockCounter 类的线程安全性,输出结果显示最终的计数者值。

序列图

sequenceDiagram
    participant Thread1
    participant Thread2
    participant Counter

    Thread1->>Counter: increment()
    activate Counter
    Counter-->>Thread1: count++
    deactivate Counter
    Thread2->>Counter: increment()
    activate Counter
    Counter-->>Thread2: count++
    deactivate Counter

类图

classDiagram
    class Counter {
        -int count
        +void increment()
        +int getCount()
    }
    class LockCounter {
        -int count
        -Lock lock
        +void increment()
        +int getCount()
    }

总结

在本文中,我们详细介绍了如何在 Java 中使用 synchronizedLock,包括各自的优缺点和使用代码示例。synchronized 简洁,易于使用,但在某些情况下灵活性不够;而 Lock 提供了更高的灵活性及控制,不过需要更小心地手动释放锁。熟练掌握这两种技术,可以帮助你在多线程环境下构建出更稳定、安全的应用。