likes
comments
collection
share

Java中的同步与锁机制详解

作者站长头像
站长
· 阅读数 28

作为Java程序员,我们都知道在编写多线程程序时,需要确保线程之间的同步与互斥。本文将详细介绍Java中的同步与锁机制。

1. 为什么需要同步与锁?

在多线程环境中,如果多个线程同时访问共享资源,可能会导致数据不一致或其他不可预料的结果。为了解决这个问题,Java提供了同步与锁机制来确保线程安全地访问共享资源。

2. Java中的同步

在Java中,同步可以通过以下两种方式实现:

  • 同步方法:使用synchronized关键字修饰方法
  • 同步块:使用synchronized关键字创建一个同步代码块

2.1 同步方法

当一个方法被synchronized关键字修饰时,同一时间只有一个线程可以执行该方法。其他线程必须等待当前线程执行完毕后才能继续执行。示例如下:


public synchronized void synchronizedMethod() {
    // 方法体
}

2.2 同步块

当需要同步的代码只是方法的一部分时,可以使用同步块。示例如下:


public void someMethod() {
    // 非同步代码

    synchronized (this) {
        // 同步代码块
    }

    // 非同步代码
}

3. Java中的锁

Java提供了java.util.concurrent.locks包,其中包含了多种锁机制。最常用的锁是ReentrantLock ,它实现了Lock接口。使用ReentrantLock可以实现更灵活的锁定策略,包括可重入锁、公平锁和非公平锁。

可重入锁、公平锁和非公平锁是 Java 并发编程中的几种锁类型

  1. 可重入锁(Reentrant Lock)

可重入锁是指在一个线程已经获得锁的情况下,该线程可以再次获得同一个锁。这种锁的优点是可以避免死锁和提高代码的可重用性。Java 中的 synchronizedReentrantLock 都是可重入锁的实现。

class ReentrantExample {
    private final ReentrantLock lock = new ReentrantLock();

    void methodA() {
        lock.lock();
        try {
            // 执行方法 A 的代码
            methodB();
        } finally {
            lock.unlock();
        }
    }

    void methodB() {
        lock.lock();
        try {
            // 执行方法 B 的代码
        } finally {
            lock.unlock();
        }
    }
}

在这个示例中,当线程执行 methodA() 并获得锁时,它可以在 methodA() 内部调用 methodB() 并再次获得同一个锁,因为 ReentrantLock 是可重入的

2.公平锁(Fair Lock)

公平锁是指在锁的获取顺序上遵循先进先出(FIFO)原则,先请求锁的线程会先获得锁。这种锁可以避免线程饥饿现象,但是相对于非公平锁,公平锁的性能开销较大。在 Java 中,ReentrantLock 可以通过构造函数参数设置为公平锁。

class FairLockExample {
    private final ReentrantLock fairLock = new ReentrantLock(true);

    void method() {
        fairLock.lock();
        try {
            // 执行方法的代码
        } finally {
            fairLock.unlock();
        }
    }
}

在这个示例中,ReentrantLock的构造函数接收一个布尔参数 true,表示锁是公平的。这样,锁的获取顺序将遵循先进先出原则。

3.非公平锁(Unfair Lock)

非公平锁与公平锁相反,它不保证锁获取的顺序。当一个线程释放锁后,其他等待线程中可能有优先级更高的线程优先获得锁。非公平锁的优点是性能较好,因为它不需要维护等待线程的顺序。Java 中的 ReentrantLock 默认为非公平锁。

class UnfairLockExample {
    private final ReentrantLock unfairLock = new ReentrantLock(false);

    void method() {
        unfairLock.lock();
        try {
            // 执行方法的代码
        } finally {
            unfairLock.unlock();
        }
    }
}

在这个示例中,ReentrantLock的构造函数接收一个布尔参数 false,表示锁是非公平的。这样,锁的获取顺序将不遵循先进先出原则,可能导致线程饥饿现象,但在大多数情况下性能较好。

简单来说就是

  • 可重入锁:允许线程在已经获得锁的情况下再次获得同一个锁,可以避免死锁和提高代码的可重用性。
  • 公平锁:遵循先进先出原则,先请求锁的线程会先获得锁,避免线程饥饿现象,但性能开销较大。
  • 非公平锁:不保证锁获取的顺序,性能较好,但可能导致线程饥饿现象。

在实际应用中,选择锁类型取决于具体场景和性能需求。在大多数情况下,默认的非公平锁性能较好,可以满足需求;但如果需要确保线程不会饥饿,可以选择公平锁。

4. 死锁与如何避免

死锁是指多个线程相互等待对方释放资源的情况。这会导致程序无法继续执行。为了避免死锁,可以采取以下策略:

  • 按顺序请求资源
  • 设置超时释放锁
  • 使用死锁检测算法

常见线程面试题

什么是线程饥饿?

线程饥饿是指一个线程因为竞争资源而长时间得不到执行的现象。在某些情况下,由于调度策略或者其他因素,一些线程可能长时间无法获得所需资源,导致这些线程一直处于等待状态,无法继续执行。这种现象称为线程饥饿。

以一个简单的银行排队场景为例。假设银行有两个窗口(资源),分别由两个线程(窗口1窗口2)负责。顾客(任务)会在队列中等待,按照到达顺序依次进行服务。在正常情况下,顾客会按照到达顺序进行服务,不会出现线程饥饿现象。

但是,假设银行的窗口调度策略有问题,窗口1总是优先服务队列中的新到达顾客,窗口2则按照顺序服务。这样一来,窗口1可能会不断抢占新到达的顾客,导致队列前面的顾客一直得不到服务。这种情况下,队列前面的顾客就会出现饥饿现象,无法得到服务。

为了解决线程饥饿问题,可以采用公平的调度策略。在上述银行排队场景中,如果两个窗口都按照先进先出的原则服务顾客,那么线程饥饿现象将不会发生,每个顾客都能按照到达顺序得到服务。

在Java中,使用公平锁(如ReentrantLock的公平锁模式)可以避免线程饥饿现象。公平锁会确保等待时间最长的线程最先获得锁,从而避免某些线程长时间得不到锁,导致线程饥饿。但是,公平锁的性能开销通常比非公平锁要大,因为需要维护一个队列来记录等待线程的顺序。

什么是Java中的同步?

答:Java中的同步是一种机制,用于确保多个线程在共享资源上按照预期的顺序访问,以防止竞争条件和数据不一致。

什么是锁?

答:锁是一种并发控制工具,用于确保同一时刻只有一个线程可以访问共享资源。Java提供了内置锁(通过synchronized关键字)和显式锁(通过java.util.concurrent.locks包中的类,例如ReentrantLock)。

解释Java中的synchronized关键字。

答:synchronized是Java中的一个关键字,用于表示一个方法或代码块需要同步执行。当一个线程进入一个synchronized方法或代码块时,它会获得与该对象或类关联的锁,而其他线程必须等待该锁被释放才能执行相同的方法或代码块。

什么是死锁?如何避免死锁?

答:死锁是一种并发问题,发生在两个或多个线程相互等待对方释放资源的情况下。避免死锁的方法包括:

  • 避免循环等待:按照一定的顺序请求锁。
  • 使用锁超时:尝试获取锁时设置超时时间,超时后线程可以释放已持有的锁并重试。
  • 使用可中断锁:使用java.util.concurrent.locks包中的锁,如ReentrantLock,它们可以响应中断信号,从而允许线程放弃等待并释放锁。

什么是可重入锁?

答:可重入锁(ReentrantLock)是一种锁,允许线程多次获取同一个锁而不会导致死锁。当一个线程已经持有锁时,它可以再次获取该锁而不会被阻塞。Java中的synchronized关键字和ReentrantLock类提供了可重入锁的实现。

解释java.util.concurrent.locks包中的Lock接口和ReentrantLock类。

答:Lock接口是Java并发包中提供的一个显式锁机制。它定义了用于获取和释放锁的方法。 ReentrantLockLock接口的一个实现,它提供了可重入的互斥锁功能。与synchronized关键字相比, ReentrantLock提供了更高的灵活性。

转载自:https://juejin.cn/post/7216570675704037432
评论
请登录