java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java中Lock使用

Java中的Lock使用实例详解

作者:Arva .

在多线程编程中,锁是保证数据一致性的关键工具,Java从JDK1.5开始提供了java.util.concurrent.locks包,其中的Lock接口及其实现类比传统的synchronized更加强大和灵活,这篇文章主要介绍了Java中Lock使用的相关资料,需要的朋友可以参考下

1. 为什么需要 Lock?(从 synchronized 说起)

synchronized 的局限性:
synchronized 用起来很简单,但它有一些“笨拙”的地方:

为了解决这些“笨拙”的问题,Java 在 1.5 版本引入了 java.util.concurrent.locks.Lock 接口。

2. Lock 是什么?

3. Lock 的核心方法

Lock 接口最主要的方法有以下几个:

  1. lock(): 获取锁。如果锁被其他线程占用,则当前线程会一直等待,直到拿到锁为止。这是最基础的用法。
  2. unlock(): 释放锁非常重要! 你必须手动调用,通常放在 finally 块中以确保一定会被执行,防止死锁。
  3. tryLock(): 尝试获取锁。它不会像 lock() 那样死等。如果能拿到锁,就返回 true;如果拿不到,就立刻返回 false。这给了你“抢不到就去做别的事”的可能性。
  4. tryLock(long time, TimeUnit unit): 带超时的尝试获取锁。在指定的时间内尝试获取锁,获取成功则返回 true,超时后还获取不到则返回 false。这个方法非常实用,可以避免线程无限期等待。
  5. lockInterruptibly(): 可中断地获取锁。在等待锁的过程中,线程可以被中断(调用 interrupt() 方法)。

4. 最常用的实现类:ReentrantLock

ReentrantLockLock 接口最主要、最常用的实现类。它的名字叫“可重入锁”,意思是同一个线程可以多次获取同一把锁而不会把自己锁死(和 synchronized 一样)。

基础使用模板

使用 Lock 有一个固定的“套路”,以确保锁一定能被释放:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
    // 1. 创建一个 Lock 实例,通常是 ReentrantLock
    private final Lock lock = new ReentrantLock();
    public void safeMethod() {
        // 2. 在操作共享资源前,获取锁
        lock.lock();
        try {
            // 3. 在这里执行需要线程安全的代码(临界区代码)
            // ... 比如修改一个共享变量
            System.out.println(Thread.currentThread().getName() + " 拿到了锁");
        } finally {
            // 4. 在 finally 块中释放锁,确保无论发生什么异常,锁都会被释放
            lock.unlock();
            System.out.println(Thread.currentThread().getName() + " 释放了锁");
        }
    }
}

关键点: lock.unlock() 一定要放在 finally 块里!否则如果临界区代码发生异常,锁可能永远无法释放,导致其他线程全部“饿死”。

高级用法示例:tryLock

public void tryLockExample() {
    if (lock.tryLock()) { // 尝试获取锁
        try {
            // 成功拿到锁,执行任务
            System.out.println(Thread.currentThread().getName() + " 成功获取锁,执行任务...");
            Thread.sleep(1000); // 模拟任务执行时间
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    } else {
        // 没拿到锁,去做别的事情
        System.out.println(Thread.currentThread().getName() + " 获取锁失败,去执行其他任务了...");
    }
}

ReentrantLock基于AQS,AQS有tryAcquire(),所以ReentrantLock有tryLock()尝试加锁

5. Lock 的另一个强大功能:条件(Condition)

synchronized 的等待/通知

synchronized 中,我们是这样让线程等待和唤醒的:

synchronized (obj) {
    while (条件不满足) {
        obj.wait();  // 线程等待
    }
    // 执行任务...
}
// 另一个线程中
synchronized (obj) {
    obj.notify();  // 唤醒一个等待的线程
    // 或者 obj.notifyAll(); 唤醒所有等待的线程
}

问题:所有线程都在同一个"等待队列"里,我们无法精确控制唤醒哪种线程。

Condition 就像"专门的等待室"

想象一个餐厅:

代码对比理解

import java.util.concurrent.locks.*;
public class Restaurant {
    private final Lock lock = new ReentrantLock();
    // 创建两个专门的"等候室"
    private final Condition notFull = lock.newCondition();  // "桌子有空位"等候室
    private final Condition notEmpty = lock.newCondition(); // "有菜可吃"等候室
    private int foodCount = 0;
    private final int MAX_FOOD = 5;
    // 厨师(生产者):做菜
    public void cook() throws InterruptedException {
        lock.lock();
        try {
            while (foodCount == MAX_FOOD) {
                System.out.println("厨房满了,厨师在notFull等候室等待...");
                notFull.await();  // 如果厨房满了,就去"桌子有空位"等候室等待
            }
            // 做菜
            foodCount++;
            System.out.println("厨师做了一道菜,现在有 " + foodCount + " 道菜");
            // 通知在"有菜可吃"等候室等待的客人
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }
    // 客人(消费者):吃菜
    public void eat() throws InterruptedException {
        lock.lock();
        try {
            while (foodCount == 0) {
                System.out.println("没菜了,客人在notEmpty等候室等待...");
                notEmpty.await();  // 如果没菜了,就去"有菜可吃"等候室等待
            }
            // 吃菜
            foodCount--;
            System.out.println("客人吃了一道菜,还剩 " + foodCount + " 道菜");
            // 通知在"桌子有空位"等候室等待的厨师
            notFull.signal();
        } finally {
            lock.unlock();
        }
    }
}

synchronized 配合 wait()notify() 可以实现线程间的等待/通知机制。Lock 也有对应的、但更强大的功能,那就是 Condition

你可以通过 LocknewCondition() 方法创建一个 Condition 对象。一个 Lock 可以创建多个 Condition,这意味着你可以有多个等待队列,实现更精细的线程控制。

经典场景:生产者-消费者模型
你可以创建两个 Condition:notFull(队列未满)和 notEmpty(队列未空)。

这样比单一的 wait()/notify() 更清晰,不容易出错。

6. 公平锁 vs 非公平锁

在创建 ReentrantLock 时,可以传入一个 boolean 参数来指定是否是公平锁:

优缺点:

在绝大多数情况下,使用默认的非公平锁即可,因为性能更好。

7. 总结:Lock 和 synchronized 如何选择?

特性synchronizedLock
本质Java 语言关键字,内置的是一个接口,需要手动实例化
用法简单,自动加锁解锁灵活,手动控制,需配合 try-finally
可中断是 (lockInterruptibly)
超时获取是 (tryLock(long, TimeUnit))
公平性非公平两者都可选(默认非公平)
** Condition **单一多个

选择建议:

到此这篇关于Java中Lock使用详解的文章就介绍到这了,更多相关Java中Lock使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文