授课语音

Java的synchronized和Lock

一、引言

在多线程编程中,当多个线程同时访问共享资源时,可能会发生数据冲突和不一致的情况。为了解决这一问题,我们通常使用同步机制来确保同一时刻只有一个线程可以访问关键代码区域。Java 中提供了两种主要的同步机制:synchronizedLock。它们各有优缺点,适用于不同的场景。


二、synchronized关键字

1. synchronized简介

synchronized 是 Java 内置的同步机制,它可以用来确保同一时间只有一个线程执行某个方法或代码块,从而避免数据冲突。

  • 通过在方法或代码块上添加 synchronized 关键字,可以将其设为同步方法或同步代码块。
  • 适合一些简单的同步场景,但控制力度不够细腻。

2. synchronized的应用方式

synchronized 的主要应用方式有两种:

  • 同步方法:将整个方法设置为同步方法。
  • 同步代码块:将部分代码放在 synchronized 块中,实现更精确的控制。

3. synchronized代码示例

示例 1:同步方法

public class SynchronizedExample {

    private int count = 0;

    // 定义一个同步方法,确保在同一时刻只有一个线程能执行此方法
    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

示例 2:同步代码块

public class SynchronizedBlockExample {

    private int count = 0;
    private final Object lock = new Object();

    // 使用同步代码块,锁定具体代码块
    public void increment() {
        synchronized (lock) { // 只有在锁内的代码才会被同步
            count++;
        }
    }

    public int getCount() {
        return count;
    }
}

在上述示例中,increment 方法是同步的,确保多个线程调用时,只有一个线程可以操作 count 变量。


三、Lock接口

1. Lock的简介

Lock 是 Java 并发包(java.util.concurrent.locks)提供的一个接口,用来实现更灵活的同步控制。与 synchronized 相比,Lock 提供了更多的控制和灵活性,例如可以尝试获取锁、可以在获取锁的过程中设置等待时间等。

2. Lock的主要方法

  • lock():获取锁,若锁已被其他线程占用,则等待。
  • unlock():释放锁。
  • tryLock():尝试获取锁,立即返回 truefalse,不会阻塞。
  • lockInterruptibly():在等待锁的过程中可响应中断。

3. 使用 Lock 的步骤

  1. 创建锁对象,例如 ReentrantLock
  2. 在代码块中获取锁(调用 lock())。
  3. 执行关键代码。
  4. finally 块中释放锁,确保资源安全。

4. Lock的代码示例

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

public class LockExample {

    private int count = 0;
    private final Lock lock = new ReentrantLock();

    // 使用Lock实现同步
    public void increment() {
        lock.lock(); // 获取锁
        try {
            count++;
        } finally {
            lock.unlock(); // 在finally块中释放锁,确保一定会执行
        }
    }

    public int getCount() {
        return count;
    }
}

上述代码通过 ReentrantLock 实现了线程安全。使用 lock()unlock() 进行锁的管理,可以确保只有一个线程能够访问共享资源,并且无论是否出现异常,都会释放锁。


四、synchronized和Lock的区别与比较

比较项 synchronized Lock
锁机制 内置于 Java 语言 通过 java.util.concurrent.locks 提供
灵活性 较低,自动释放锁 较高,可手动控制锁的获取与释放
锁可重入性 支持 支持(如 ReentrantLock
可中断性 不支持 支持 lockInterruptibly() 方法
超时获取锁 不支持 支持 tryLock() 方法
性能 效率较高 在高并发情况下更灵活

五、应用场景

  • synchronized:适合锁的范围和生命周期比较简单的场景,尤其是内部同步。
  • Lock:适合锁的控制更为复杂、需要高灵活性的场景,例如在高并发应用中使用,能够提供更细粒度的控制。

六、最佳实践

  1. 如果同步需求简单、明确,可以优先使用 synchronized,其内置特性让代码更易于维护。
  2. 如果同步要求较高,需要精细的锁控制,例如尝试获取锁、定时获取锁等,可以使用 Lock
  3. 在使用 Lock 时,一定要注意在 finally 块中释放锁,防止因异常而导致锁未被释放的问题。

七、综合实例

下面是一个综合应用了 synchronizedLock 的示例,展示了如何在不同场景下选择适当的同步机制。

代码示例:使用 synchronizedLock 实现并发访问

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

public class BankAccount {

    private int balance = 1000; // 银行账户余额
    private final Lock lock = new ReentrantLock(); // 创建可重入锁

    // 使用synchronized确保账户存款的线程安全
    public synchronized void deposit(int amount) {
        balance += amount;
        System.out.println(Thread.currentThread().getName() + " 存款: " + amount + " 新余额: " + balance);
    }

    // 使用Lock确保账户取款的线程安全
    public void withdraw(int amount) {
        lock.lock(); // 获取锁
        try {
            if (balance >= amount) {
                balance -= amount;
                System.out.println(Thread.currentThread().getName() + " 取款: " + amount + " 新余额: " + balance);
            } else {
                System.out.println(Thread.currentThread().getName() + " 余额不足,无法取款 " + amount);
            }
        } finally {
            lock.unlock(); // 确保锁总能释放
        }
    }

    public int getBalance() {
        return balance;
    }

    public static void main(String[] args) {
        BankAccount account = new BankAccount();

        // 创建存款线程
        Thread depositThread = new Thread(() -> account.deposit(500), "存款线程");
        
        // 创建取款线程
        Thread withdrawThread = new Thread(() -> account.withdraw(300), "取款线程");

        depositThread.start();
        withdrawThread.start();
    }
}

代码说明

  • deposit 方法使用 synchronized 关键字,使其在执行时线程安全。
  • withdraw 方法使用 Lock 进行锁管理,这样我们可以精确地控制锁的获取和释放,并能处理余额不足的情况。
  • main 方法中启动两个线程,分别执行存款和取款操作,测试线程安全。

通过这两个方法,我们可以看到 synchronizedLock 的不同使用方式。


八、总结

synchronizedLock 是 Java 中实现多线程同步的两大核心工具,各有优劣。

  • synchronized 使用简单,但灵活性较差,适用于简单同步。
  • Lock 灵活性高,适合更复杂的同步控制场景。

了解并掌握它们的使用场景,能有效提高程序的并发安全性和执行效率。

去1:1私密咨询

系列课程: