Java synchronized 、ReentrantLock和Semaphore

发布时间 2023-12-08 13:35:06作者: 先锋之客

synchronized

在Java中,使用synchronized关键字可以实现对代码块或方法的同步访问,以确保多个线程不会同时访问共享资源。当一个线程获取了对象的锁(即进入了synchronized代码块),其他线程如果也希望获取该对象的锁,它们将被阻塞,直到拥有锁的线程执行完毕并释放锁。

因此,在某种意义上,使用synchronized关键字可以导致线程阻塞。这是因为当一个线程持有锁时,其他线程在尝试获得相同锁时将会被阻塞,直到持有锁的线程释放锁。

然而,需要注意的是,线程阻塞并不是synchronized关键字的本质特性,而是由于锁的竞争和获取导致的一种现象。synchronized关键字的作用是确保对共享资源的安全访问,而阻塞是由于多个线程对锁的竞争导致的。

在实际编码中,应该谨慎地使用synchronized关键字,避免出现过多的锁竞争,从而导致性能问题和线程阻塞。同时,还可以考虑使用更灵活的并发控制工具,如ReentrantLock、Semaphore等,来避免一些synchronized可能引起的问题。

ReentrantLock

ReentrantLock是Java中的一个可重入锁(Reentrant Lock),它提供了与synchronized关键字类似的功能,但更加灵活和扩展。

与synchronized关键字不同,ReentrantLock允许线程多次获取同一个锁,而不会导致死锁。它使用计数器来跟踪锁的持有次数,每次获取锁时计数器加一,释放锁时计数器减一。只有当计数器为零时,其他线程才能获取该锁。

下面是一个简单的示例,演示了如何使用ReentrantLock来控制并发访问:

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Runnable task = () -> {
            try {
                lock.lock(); // 获取锁
                // 访问共享资源的代码
                System.out.println("Thread " + Thread.currentThread().getId() + " is accessing the shared resource");
                Thread.sleep(2000); // 模拟访问共享资源的耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock(); // 释放锁
            }
        };

        // 创建多个线程并启动
        for (int i = 0; i < 5; i++) {
            new Thread(task).start();
        }
    }
}

在这个示例中,我们创建了一个ReentrantLock实例,并在多个线程中使用lock()方法获取锁,并在访问共享资源后使用unlock()方法释放锁。

与synchronized关键字相比,ReentrantLock提供了更多的功能,比如可定时的、可中断的锁等待机制、公平性等。但需要注意的是,使用ReentrantLock需要显式地调用lock()和unlock()方法来控制锁的获取和释放,确保在异常情况下锁能够被正确释放,否则可能导致死锁。

总的来说,ReentrantLock是一种强大而灵活的锁实现,可以在需要更复杂的并发控制时使用,提供了比synchronized更多的功能选项。

Semaphore

Semaphore是Java中用于控制并发访问的工具之一,它可以限制对共享资源的访问数量。与synchronized关键字不同,Semaphore允许多个线程同时访问共享资源,但是通过控制许可证的数量来限制并发访问的线程数。

Semaphore维护了一定数量的许可证(permits),线程在访问共享资源之前必须先获得许可证,如果许可证数量耗尽,其他线程将被阻塞直到有线程释放许可证。

下面是一个简单的示例,演示了如何使用Semaphore来控制并发访问:

import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(2); // 创建一个初始许可证数量为2的Semaphore

        Runnable task = () -> {
            try {
                semaphore.acquire(); // 获取许可证
                // 访问共享资源的代码
                System.out.println("Thread " + Thread.currentThread().getId() + " is accessing the shared resource");
                Thread.sleep(2000); // 模拟访问共享资源的耗时操作
                semaphore.release(); // 释放许可证
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        // 创建多个线程并启动
        for (int i = 0; i < 5; i++) {
            new Thread(task).start();
        }
    }
}

在这个示例中,我们创建了一个初始许可证数量为2的Semaphore,然后创建了5个线程尝试访问共享资源。由于Semaphore的许可证数量限制为2,只有两个线程可以同时获得许可证并访问共享资源,其他线程将会被阻塞。

总的来说,Semaphore提供了比synchronized更灵活、更细粒度的并发访问控制,可以帮助我们更好地管理共享资源的访问,并避免一些潜在的性能问题。