高并发学习之09锁的认识

1. Java中的锁的概念

在Java中锁大致分为以下几种:

  • 自旋锁 :为了不放弃CPU执行事件,循环的使用CAS技术对数据尝试进行更新,直到成功。
  • 悲观锁:假定发生并发冲突,同步所有对数据相关的操作,从读数据就开始上锁。
  • 乐观锁:假定没有发生冲突,在修改数据时如果发现数据和之前获取的不一致,则读取最新的数据,修改后重试修改。
  • 独享锁(写):给资源写加上锁,线程可以修改资源,其他线程不能在加锁(单写)。
  • 共享锁(读):给资源加上读锁后只能读不能改,其他线程也只能加读锁,不能加写锁。
  • 可重入锁、不可重入锁:线程拿到一把锁后,可以自由的进入同一把锁所同步的其他代码。
  • 公平锁、非公平锁:争抢锁的顺序,如果是按先来后到则为公平。
2. Lock简介

我们知道,锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源,在Lock接口出现之前,Java应用程序只能依靠synchronized关键字来实现同步锁的功能,在java5以后,增加了JUC的并发包且提供了Lock接口用来实现锁的功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁。虽然它缺少了(通过synchronized块或者方法所提供的)隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。

3. Lock的初步使用

Lock是一个接口,核心的两个方法lock和unlock,下面是Lock接口源码

public interface Lock {
	//获取锁
    void lock();
	//获取锁的过程能够响应中断
    void lockInterruptibly() throws InterruptedException;
	//非阻塞式响应中断能立即返回,获取锁放回true反之返回fasle
    boolean tryLock();
	//超时获取锁,在超时内或者未中断的情况下能够获取锁
    boolean tryLock(long var1, TimeUnit var3) throws InterruptedException;
	//释放锁
    void unlock();
	//获取与lock绑定的等待通知组件,当前线程必须获得了锁才能进行等待,进行等待时会先释放锁,当再次获取锁时才能从等待中返回
    Condition newCondition();
}

它有很多的实现,比如ReentrantLock、ReentrantReadWriteLock等。下面通过分析ReentrantLock来全方位的了解Lock。

3.1 ReentrantLock(重入锁)讲解

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

 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(() -> {
                RLDemo.inc();
            }).start();
        }
        Thread.sleep(3000);
        System.out.println("result:" + count);
    }
输出结果如下:
result:1000

上面代码中main方法生成1000个线程每个线程调用自加,线程安全情况下输出1000.

3.2 ReentrantReadWriteLock(读写锁)

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

    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来模拟了一个内存缓存,然后使用读写所来保证这个内存缓存的线程安全性。当执行读操作的时候,需要获取读锁,在并发访问的时候,读锁不会被阻塞,因为读操作不会影响执行结果。
在执行写操作是,线程必须要获取写锁,当已经有线程持有写锁的情况下,当前线程会被阻塞,只有当写锁释放以后,其他读写操作才能继续执行。使用读写锁提升读操作的并发性,也保证每次写操作对所有的读写操作的可见性

  • 读锁与读锁可以共享
  • 读锁与写锁不可以共享(排他)
  • 写锁与写锁不可以共享(排他)
4. Lock和synchronized的简单对比

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值