java Lock常用锁

在Java中,Lock接口提供了比synchronized关键字更广泛的锁定操作。Lock接口有许多实现,但是最常见的是ReentrantLock和ReadWriteLock。

1. ReentrantLock:重入锁,即支持再次进入已经获得的锁,也就是说线程可以进入任何一个它已经拥有的锁所同步着的代码块。ReentrantLock提供了与synchronized相同的互斥性和内存可见性,但添加了锁投票、定时锁等候和锁中断的一些特性。适用于竞争激烈的情况,即锁竞争的线程多。

ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    // 这里被lock保护

} finally {
    lock.unlock();
}

2. ReadWriteLock:读写锁,用于提高多线程环境下读操作的效率。在没有写操作时,允许多个线程同时读,提高了并发性。适用于读多写少的情况。

ReadWriteLock rwLock = new ReentrantReadWriteLock();
rwLock.readLock().lock();
try {
    // read from the resource
} finally {
    rwLock.readLock().unlock();
}

rwLock.writeLock().lock();
try {
    // write to the resource
} finally {
    rwLock.writeLock().unlock();
}


以上两种锁都是显式锁,需要显式的获取和释放锁,这也就意味着它们都支持Lock接口定义的各种功能。

隐式锁和显式锁

隐式锁和显式锁是两种锁的获取方式,主要的区别在于锁的获取和释放方式。

1. 隐式锁:隐式锁也就是内置锁或监视器锁,是通过synchronized关键字来实现的。在Java中,每个对象都可以作为锁,这就是隐式锁,它总是与对象关联在一起。隐式锁的获取和释放是隐式的,发生在通过synchronized关键字修饰的代码块或方法的开始和结束处。因此,使用隐式锁的一个优点是无需手动释放锁,即使在异常情况下,JVM也能保证锁会被释放。

synchronized (object) {
    // access the resource
}

2. 显式锁:显式锁是通过Lock接口(及其实现类,如ReentrantLock)来实现的。显式锁需要用户手动获取和释放锁,这为锁的管理提供了更大的灵活性,但也需要更谨慎地处理锁的获取和释放,一般推荐在finally代码块中释放锁,以保证锁一定会被释放。

Lock lock = new ReentrantLock();
lock.lock();
try {
    // access the resource
} finally {
    lock.unlock();
}

总的来说,隐式锁的使用更简单,但显式锁提供了更高级的锁操作,如尝试获取锁、获取可中断锁和获取定时锁等,以及更精细的锁控制。

Lock接口及其实现(如ReentrantLock)的底层实现并不是基于synchronized关键字的。它们是基于Java的并发包java.util.concurrent中的类(主要是AbstractQueuedSynchronizer,简称AQS)来实现的。

AQS是一个用于构建锁和同步器的框架,许多同步类都使用了它,如ReentrantLock、Semaphore、CountDownLatch、ReentrantReadWriteLock等。AQS使用一个int成员变量来表示同步状态,并提供了条件对象来控制多线程的访问。

ReentrantLock支持完全互斥的排他锁和可以同时被多个线程持有的共享锁,也支持公平锁和非公平锁策略。这些特性都是通过AQS来实现的。

总的来说,Lock和synchronized都可以用来实现线程同步,但是Lock提供了更高级更灵活的线程同步机制,可以实现更复杂的同步控制。

ReadWriteLock
`ReadWriteLock`是一个常用的共享锁,它包含一对锁,一个是读锁,一个是写锁。通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升。

以下是一个简单的`ReadWriteLock`的使用示例,模拟了一个缓存系统,这个缓存系统中的读操作明显多于写操作:

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

public class Cache<K, V> {
    private final Map<K, V> map;
    private final ReadWriteLock rwl = new ReentrantReadWriteLock();
    private final Lock r = rwl.readLock();
    private final Lock w = rwl.writeLock();

    public Cache(Map<K, V> map) {
        this.map = map;
    }

    public V get(K key) {
        r.lock();
        try {
            return map.get(key);
        } finally {
            r.unlock();
        }
    }

    public V put(K key, V value) {
        w.lock();
        try {
            return map.put(key, value);
        } finally {
            w.unlock();
        }
    }

    // ... other methods go here
}

在这个例子中,`get()`方法使用读锁,在没有写操作时,可以被多个线程同时调用,提高了并发性。`put()`方法使用写锁,保证了写操作的原子性,防止了数据不一致的问题。

CountDownLatch

CountDownLatch:这是一个同步工具类,它允许一个或多个线程等待直到在其他线程中执行的一组操作完成。主要方法有`countDown()`和`await()`。使用场景通常是在某个线程需要等待一个或多个其他线程完成操作之后,它才能继续执行。

final int N = 3;
CountDownLatch latch = new CountDownLatch(N);
for (int i = 0; i < N; i++) {
    new Thread(() -> {
        System.out.println("Thread executed!");
        latch.countDown();
    }).start();
}

try {
    latch.await();  
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println("All threads executed!");

在上述代码中,主线程创建了一个计数为3的CountDownLatch,然后创建三个子线程,每个子线程在执行完后调用`countDown()`方法。主线程通过`await()`方法等待计数器归后,继续往下执行。

Semaphore

Semaphore:这是一个计数信号量,主要用于控制同时访问特定资源的线程数量,或者同时执行某个指定操作的线程数量。主要方法有`acquire()`和`release()`。

final int N = 3;
Semaphore semaphore = new Semaphore(N);
for (int i = 0; i < 6; i++) {
    new Thread(() -> {
        try {
            semaphore.acquire();
            System.out.println("Thread is running");
            Thread.sleep(200);
            System.out.println("Thread finished");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            semaphore.release();
        }
    }).start();
}

在上述代码中,创建了一个允许同时运行的线程数量为3的Semaphore,然后创建了6个线程。每个线程在开始运行前都需要获取许可,如果当前已有3个线程在运行,那么新的线程就需要等待。当一个线程完成后,释放许可,这时等待的线程就可以获取许可并开始运行。

公平锁和非公平锁

公平锁和非公平锁是两种锁的获取方式,主要体现在多个线程获取锁的顺序上。

1. 公平锁:公平锁是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到。在公平锁中,锁维护了一个有顺序的等待队列,所有新的线程都必须加入队列的尾部,只有队列首部的线程能获得锁。这样就能保证锁的获取是按照FIFO(先进先出)的顺序,所以也叫公平锁。

2. 非公平锁:非公平锁则是一种获取锁的抢占机制,即无视等待队列,只要CPU资源允许,线程就会尽量去获取锁,那么有可能导致先来的线程反而无法获得锁,后来的线程反而先获得了锁,出现“插队”的情况。

ReentrantLock和ReentrantReadWriteLock默认是非公平锁,但是也可以设置成为公平锁。

例如,创建一个公平的ReentrantLock,可以这样做:

Lock lock = new ReentrantLock(true); // true表示这个锁是公平的

公平锁每次都是从队列中取出最先加入队列的线程进行服务,公平性看起来非常合理,但实际上公平锁的性能会比非公平锁差很多,因为每次获取和释放锁都需要操作整个队列,而非公平锁在大部分情况下都不需要操作队列。在高并发的情况下,公平锁的性能瓶颈会更加明显。所以,在不需要维持线程执行顺序的情况下,通常推荐使用非公平锁。
 

  • 20
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值