synchronized 与 ReentrantLock 对比

synchronizedReentrantLock 是 Java 提供的两种实现线程同步的方式,它们都能够解决多线程访问共享资源时的数据一致性问题。虽然功能相似,但它们在实现机制、性能和灵活性方面存在显著差异。

1. 基本介绍

synchronized

  • 类型:Java 的关键字,内置锁。
  • 特性:简单易用,代码结构清晰。在方法或代码块上加锁,自动管理锁的获取和释放。JVM 提供了性能优化(偏向锁、轻量级锁等)。

ReentrantLock

  • 类型:显式锁(java.util.concurrent.locks 包)。
  • 特性:提供更灵活的锁功能。手动控制锁的获取与释放,支持条件变量、可中断锁和超时机制。需要正确使用 lock() 和 unlock(),否则可能导致死锁。

2. 区别对比

特性

synchronized

ReentrantLock

实现类型

JVM 内置,依赖 Monitor 对象实现

显式锁,基于 AbstractQueuedSynchronizer (AQS)实现

锁的获取和释放

自动获取和释放,线程退出同步块后自动释放锁

手动获取(lock())和释放(unlock()),需要显式调用

可中断性

不支持

支持可中断锁,通过 lockInterruptibly()中断线程等待

公平性

不支持

支持公平锁和非公平锁(默认非公平),通过构造方法设置

性能

JDK 1.6 后性能接近,但锁粒度较粗

性能高,灵活性强,适合高并发场景

锁重入性

支持(同一线程可以多次获取锁)

支持(同一线程可以多次获取锁)

锁超时

不支持

支持,通过 tryLock(long timeout, TimeUnit unit)方法

条件变量支持

不支持

支持条件变量(Condition),可以实现更复杂的线程协调

锁升级/优化机制

偏向锁、轻量级锁、重量级锁动态升级

无锁优化机制,直接基于 AQS 实现

适用场景

简单场景,代码结构清晰,性能优化后适合大多数场景

高并发场景,复杂线程协调需求

3. 具体功能对比

(1) 可中断性

  • synchronized:当一个线程在等待 synchronized 锁时,如果被阻塞,无法被中断。
  • ReentrantLock:可以通过 lockInterruptibly() 方法实现可中断的锁机制,在等待锁时可以响应中断信号。

示例:

lock.lockInterruptibly(); // 可响应中断信号

(2) 公平性

  • synchronized:无法保证公平性,线程获取锁的顺序不一定按请求顺序。
  • ReentrantLock:支持公平锁和非公平锁(默认非公平锁)。公平锁通过构造函数设置:

(3) 锁超时

  • synchronized:线程无法设置超时时间,只能等待锁释放。
  • ReentrantLock:支持超时机制,通过 tryLock(long timeout, TimeUnit unit) 实现。

示例:

if (lock.tryLock(1, TimeUnit.SECONDS)) {
    try {
        // 获得锁后的操作
    } finally {
        lock.unlock();
    }
} else {
    // 超时未获取锁的处理
}

(4) 条件变量

  • synchronized:不支持条件变量,线程只能通过 wait()notify() 进行协调。
  • ReentrantLock:支持多个条件变量,通过 Condition 实现更复杂的线程协作。

示例:

Condition condition = lock.newCondition();

lock.lock();
try {
    condition.await();  // 等待信号
    condition.signal(); // 发出信号
} finally {
    lock.unlock();
}

(5) 性能

  • synchronized:JDK 1.6 后性能大幅优化,支持偏向锁、轻量级锁等,适合大多数场景。
  • ReentrantLock:性能高,更适合高并发和需要额外功能(如超时、条件变量)的复杂场景。

4. 使用示例

(1) 使用 synchronized

public class SynchronizedExample {
    private int counter = 0;

    public synchronized void increment() {
        counter++;
    }

    public synchronized int getCounter() {
        return counter;
    }
}

(2) 使用 ReentrantLock

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private final ReentrantLock lock = new ReentrantLock();
    private int counter = 0;

    public void increment() {
        lock.lock(); // 获取锁
        try {
            counter++;
        } finally {
            lock.unlock(); // 确保锁被释放
        }
    }

    public int getCounter() {
        lock.lock();
        try {
            return counter;
        } finally {
            lock.unlock();
        }
    }
}

(3) 使用条件变量

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

public class ConditionExample {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();

    public void awaitSignal() throws InterruptedException {
        lock.lock();
        try {
            System.out.println("Waiting for signal...");
            condition.await(); // 等待信号
            System.out.println("Signal received!");
        } finally {
            lock.unlock();
        }
    }

    public void sendSignal() {
        lock.lock();
        try {
            System.out.println("Sending signal...");
            condition.signal(); // 发出信号
        } finally {
            lock.unlock();
        }
    }
}

5. 选择建议

  • 选择 synchronized 的场景:同步逻辑简单,代码可读性更重要。不需要锁的高级功能(如条件变量、可中断锁、超时锁)。小型项目或低并发场景。
  • 选择 ReentrantLock 的场景:高并发场景,需更高的性能和灵活性。需要公平锁、可中断锁、超时锁等高级功能。需要使用条件变量实现复杂的线程协作。
Java碎碎念 文章被收录于专栏

来一杯咖啡,聊聊Java的碎碎念呀

全部评论

相关推荐

编程语言的类型系统是定义如何在程序中定义和使用数据类型的一套规则和机制。不同的类型系统在处理类型的方式上有很大差异,以下是几种主要的类型系统分类及其特点:https://www.nowcoder.com/issue/tutorial?zhuanlanId=j572L2&uuid=970d62d75dbb4c7ca7161eb983d6f29a1. 静态类型系统 vs 动态类型系统静态类型系统:在编译时检查类型。变量的类型在编写代码时就确定,类型错误会在编译时被捕获。优势:可以及早发现错误并提供更好的性能(由于类型信息在运行时已知)。示例语言:Java、C、C++、Rust、Haskell。动态类型系统:在运行时检查类型。变量的类型可以在运行时改变,类型错误在运行时才会被捕获。优势:编写代码时更灵活,可以在运行时处理不同类型的数据。示例语言:Python、JavaScript、Ruby、PHP。2. 强类型 vs 弱类型强类型:不允许进行隐式类型转换,类型之间的操作需要显式转换。不同类型的值不能直接进行操作,如果尝试这样做,则会导致错误。示例语言:Python、Java、Haskell。弱类型:允许进行隐式类型转换,能够比较和操作不同类型的值。执行时会尝试自动进行类型转换,可能会导致意想不到的行为。示例语言:JavaScript、PHP、Perl。3. 显式类型 vs 隐式类型显式类型:程序员需要在定义变量时显式声明变量的类型。例如:在 Java 中,声明一个整数必须明确指定类型 int a = 5;。隐式类型:不需要在定义变量时指定类型,编译器或解释器会根据赋值自动推断类型。示例语言:Python 和 JavaScript,以下示例在 Python 中定义变量时不需要声明类型:4. 复合类型 vs 原始类型原始类型(基本类型):是语言内置的类型,通常包括整数、浮点数、字符和布尔值等。示例:Java 的 int、float、char,Python 的 int、float、str。复合类型:由原始类型组合而成的类型。包括数组、集合、字典、结构体等。示例语言:C 的结构体(struct)、Java 的对象、Python 的列表(list)和字典(dict)等。
2025-03-11
在牛客打卡295天,今天也很努力鸭!
点赞 评论 收藏
分享
评论
3
6
分享

创作者周榜

更多
正在热议
更多
牛客网
牛客企业服务