并发编程第三篇

本文详细分析了Java并发编程中的ReentrantLock,包括其与synchronized的区别、内部实现机制AQS(AbstractQueuedSynchronizer)以及公平锁与非公平锁的工作原理。通过源码解析,阐述了ReentrantLock的lock和unlock方法,以及Condition的使用,展示了ReentrantLock在提升并发性能和提供灵活锁控制方面的优势。
摘要由CSDN通过智能技术生成

同步锁

我们知道,锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源, 在Lock接口出现之前,Java应用程序只能依靠synchronized关键字来实现同步锁的功能,在java5以后,增加了JUC 的并发包且提供了Lock接口用来实现锁的功能,它提供了与synchroinzed关键字类似的同步功能,只是它比 synchronized更灵活,能够显示的获取和释放锁。

Lock是一个接口,核心的两个方法lock和unlock,它有很多的实现,比如ReentrantLock、 ReentrantReadWriteLock;

ReentrantLock

重入锁,表示支持重新进入的锁,也就是说,如果当前线程t1通过调用lock方法获取了锁之后,再次调用lock,是不会再阻塞去获取锁的,直接增加重试次数就行了。

public class AtomicDemo {

    private static int count = 0;

    static Lock lock = new ReentrantLock();

    public static void inc(){
        lock.lock();
        try{
            Thread.sleep(1);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        count++;
        lock.unlock();
    }

    public static void main(String[] args) throws InterruptedException {
        for(int i = 0;i<1000;i++){
            new Thread(()->{AtomicDemo.inc();}).start();
        }
        Thread.sleep(3000);
        System.out.println("result :" +count);
    }
}

//运行结果1000

ReentrantReadWriteLock

我们以前理解的锁,基本都是排他锁,也就是这些锁在同一时刻只允许一个线程进行访问,而读写所在同一时刻可以允许多个线程访问,但是在写线程访问时,所有的读线程和其他写线程都会被阻塞。读写锁维护了一对锁,一个 读锁、一个写锁; 一般情况下,读写锁的性能都会比排它锁好,因为大多数场景读是多于写的。在读多于写的情况 下,读写锁能够提供比排它锁更好的并发性和吞吐量.

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class LockDemo {
    static Map<String,Object> cacheMap = new HashMap<>();
    static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    static Lock read = rwl.readLock();
    static Lock write = rwl.writeLock();

    public static final Object get(String key){
        System.out.println("开始读取数据");
        read.lock();//读锁
        try{
            return cacheMap.get(key);
        }finally {
            read.unlock();
        }
    }

    public static final Object put(String key,Object value){
        write.lock();
        System.out.println("开始写数据");
        try{
            return cacheMap.put(key,value);
        }finally {
            write.unlock();
        }
    }
}

在这个案例中,通过hashmap来模拟了一个内存缓存,然后使用读写所来保证这个内存缓存的线程安全性。当执 行读操作的时候,需要获取读锁,在并发访问的时候,读锁不会被阻塞,因为读操作不会影响执行结果。

在执行写操作是,线程必须要获取写锁,当已经有线程持有写锁的情况下,当前线程会被阻塞,只有当写锁释放以 后,其他读写操作才能继续执行。使用读写锁提升读操作的并发性,也保证每次写操作对所有的读写操作的可见性。

读锁与读锁可以共享
读锁与写锁不可以共享(排他)
写锁与写锁不可以共享(排他)

Lock和synchronized的简单对比
通过我们对Lock的使用以及对synchronized的了解,基本上可以对比出这两种锁的区别了。因为这个也是在面试过程中比较常见的问题

从层次上,一个是关键字、一个是类, 这是直观的差异;
从使用上,lock具备更大的灵活性,可以控制锁的释放和获取;
synchronized的锁的释放是被动的,当出现异常或者同步代码块执行完以后,才会释放锁;
lock可以判断锁的状态、而synchronized无法做到
lock可以实现公平锁、非公平锁; 而synchronized只有非公平锁

AQS
Lock之所以能实现线程安全的锁,主要的核心是 :
AQS(AbstractQueuedSynchronizer),AbstractQueuedSynchronizer提供了一个FIFO队列,可以看做是一个用来实现锁以及其他需要同步功能的框架。这里简称该类为AQS。AQS的使用依靠继承来完成,子类通过继承自AQS并实现所需的方法来管理同步状态。例如常见的ReentrantLock,CountDownLatch等AQS的两种功能;
从使用上来说,AQS的功能可以分为两种:独占和共享。

独占锁模式下,每次只能有一个线程持有锁,比如前面给大家演示的ReentrantLock就是以独占方式实现的互斥锁;
共享锁模式下,允许多个线程同时获取锁,并发访问共享资源,比如ReentrantReadWriteLock。

很显然,独占锁是一种悲观保守的加锁策略,它限制了读/读冲突,如果某个只读线程获取锁,则其他读线程都只 能等待,这种情况下就限制了不必要的并发性,因为读操作并不会影响数据的一致性。共享锁则是一种乐观锁,它 放宽了加锁策略,允许多个执行读操作的线程同时访问共享资源 。

AQS的内部实现
同步器依赖内部的同步队列(一个FIFO双向队列)来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成为一个节点(Node)并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时&#

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值