Java线程加锁

简介

在多线程编程中,为了保证数据的一致性和避免竞态条件,我们需要使用线程加锁机制来实现同步。Java提供了多种方式来实现线程加锁,包括使用synchronized关键字,以及使用Lock接口及其实现类。本文将介绍这些方式的使用方法及其适用场景。

synchronized关键字

使用synchronized关键字可以给方法或代码块加锁,保证同一时间只有一个线程可以执行被锁住的代码。synchronized关键字有两种使用方式:作用于方法和作用于代码块。

作用于方法

public synchronized void synchronizedMethod() {
    // 代码逻辑
}

上述代码中的synchronizedMethod方法被synchronized修饰,表示该方法是一个同步方法。当一个线程进入该方法时,会自动获取该对象的锁,其他线程将被阻塞,直到该线程执行完毕释放锁。

作用于代码块

public void synchronizedBlock() {
    synchronized (this) {
        // 代码逻辑
    }
}

上述代码中的synchronized关键字作用于代码块,括号中的表达式指定了要获取锁的对象,这里使用的是this,表示当前对象。与作用于方法相同,当一个线程执行该代码块时,会获取当前对象的锁。

synchronized关键字也可以用于静态方法和类对象上,来实现对静态资源的加锁。

Lock接口

除了synchronized关键字,Java还提供了Lock接口及其实现类,用于实现更加灵活的线程加锁。

ReentrantLock

ReentrantLock是Lock接口的一个实现类,它提供了与synchronized相同的功能,但是使用起来更加灵活。使用ReentrantLock需要在加锁代码块外部定义一个Lock对象。

Lock lock = new ReentrantLock();

public void lockMethod() {
    lock.lock();
    try {
        // 代码逻辑
    } finally {
        lock.unlock();
    }
}

上述代码中,首先创建了一个ReentrantLock对象lock,然后在lockMethod方法中使用lock.lock()和lock.unlock()方法来实现加锁和释放锁的操作。与synchronized不同,使用Lock接口时需要手动释放锁,所以通常将其放在finally块中确保锁一定会被释放。

适用场景

synchronized关键字和Lock接口都可以实现线程加锁,但在不同的场景下使用的选择可能有所不同。

  • synchronized关键字是Java的内置关键字,使用起来更加简单,在大多数情况下可以满足需求。
  • 当需要实现一些高级的线程同步功能时,如读写锁、可中断锁、公平锁等,可以使用Lock接口的实现类。
  • 在竞争激烈的场景下,使用Lock接口通常比synchronized关键字效果更好。

甘特图

下面是一个使用mermaid语法表示的甘特图,展示了线程加锁的过程:

gantt
    dateFormat  YYYY-MM-DD
    title       线程加锁甘特图

    section synchronized关键字
    任务1          :done, 2021-09-01, 1d
    任务2          :done, 2021-09-02, 2d
    任务3          :active, 2021-09-04, 3d
    任务4          :           2021-09-07, 4d

    section Lock接口
    任务1          :done, 2021-09-01, 1d
    任务2          :done, 2021-09-02, 2d
    任务3          :active, 2021-09-05, 3d
    任务4          :           2021-09-08, 4d

总结

本文介绍了Java中线程加锁的两种方式:synchronized关键字和Lock接口。synchronized关键字简单易