返回

深入探讨Java并发:可重入锁、读写锁、(非)公平锁的全面解析

后端

在专栏前面的文章——《线程并发的同步控制》中,我们已经学过如何用synchronized加锁进行同步控制了。不过,在Java中除了synchronized外,还有可重入锁(ReentrantLock)、读写锁(ReadWriteLock)等锁类型,它们具有不同的特性,能够满足不同的并发编程需求。本文将对这些锁进行全面解析,帮助读者深入理解并掌握Java并发编程技术。

可重入锁

可重入锁(ReentrantLock)是一种互斥锁,它允许同一个线程对同一个锁对象进行多次加锁。当线程再次试图对已加锁的锁对象进行加锁时,不会被阻塞,而是会递增锁的持有计数。当线程完成锁定的操作后,需要对锁对象进行相同的次数的解锁操作,使持有计数减为0,才能释放锁。可重入锁的优势在于它避免了线程死锁的风险,因为它允许同一个线程对同一个锁对象进行多次加锁。

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {

    private ReentrantLock lock = new ReentrantLock();
    private int counter = 0;

    public void incrementCounter() {
        lock.lock();
        try {
            counter++;
        } finally {
            lock.unlock();
        }
    }

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

    public static void main(String[] args) {
        ReentrantLockExample example = new ReentrantLockExample();
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.incrementCounter();
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.incrementCounter();
            }
        });

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final counter value: " + example.getCounter());
    }
}

读写锁

读写锁(ReadWriteLock)是一种允许多个线程同时读写共享数据的锁。读写锁分为读锁和写锁,读锁允许多个线程同时获取,而写锁只能被一个线程获取。当一个线程获取写锁后,其他线程不能获取读锁或写锁,直到写锁释放。读写锁的优势在于它提高了并发读取的性能,同时保证了写操作的独占性。

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockExample {

    private ReadWriteLock lock = new ReentrantReadWriteLock();
    private int counter = 0;

    public void incrementCounter() {
        lock.writeLock().lock();
        try {
            counter++;
        } finally {
            lock.writeLock().unlock();
        }
    }

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

    public static void main(String[] args) {
        ReadWriteLockExample example = new ReadWriteLockExample();
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.incrementCounter();
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.getCounter();
            }
        });

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final counter value: " + example.getCounter());
    }
}

公平锁与非公平锁

公平锁和非公平锁都是指可重入锁的两种实现方式。公平锁保证了线程获取锁的顺序与它们请求锁的顺序一致,而非公平锁则没有这样的保证。当多个线程同时请求同一个锁时,公平锁会按照它们请求锁的顺序来获取锁,而非公平锁则可能让后请求锁的线程先获取锁。公平锁的优势在于它避免了饥饿现象的发生,而非公平锁的优势在于它在某些场景下具有更好的性能。

import java.util.concurrent.locks.ReentrantLock;

public class FairLockExample {

    private ReentrantLock lock = new ReentrantLock(true);
    // true表示公平锁,false表示非公平锁

    private int counter = 0;

    public void incrementCounter() {
        lock.lock();
        try {
            counter++;
        } finally {
            lock.unlock();
        }
    }

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

    public static void main(String[] args) {
        FairLockExample example = new FairLockExample();
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.incrementCounter();
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.incrementCounter();
            }
        });

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final counter value: " + example.getCounter());
    }
}

总结

可重入锁、读写锁、公平锁和非公平锁都是Java并发编程中的重要锁类型,它们具有不同的特性和适用场景。可重入锁允许同一个线程对同一个锁对象进行多次加锁,避免了线程死锁的风险。读写锁允许多个线程同时读写共享数据,提高了并发读取的性能,同时保证了写操作的独占性。公平锁和非公平锁都是指可重入锁的两种实现方式,公平锁保证了线程获取锁的顺序与它们请求锁的顺序一致,而非公平锁则没有这样的保证。