【多线程】JAVA中的锁

锁作用

java中的锁是用来控制多个线程访问共享资源的方式。一般来说一个锁能够防止多个线程同时访问共享资源(读写锁,读锁是共享锁允许多个线程读共享资源)。

锁的分类

  • 乐观锁:在访问资源时,认为竞争不总是存在,所以在访问共享资源时不加锁,而是在更新数据时判断共享资源是否被其他线程修改(根据版本号/时间戳),如果没有则修改成功;否则进行重试或报错。
  • 悲观锁:在访问资源时,任务竞争总是存在,所以在访问共享资源时进行加锁。
  • 共享锁:访问共享资源时,共享锁可以被多个线程共同持有。
  • 排它锁:访问共享资源时,锁只能被一个线程所持有。

锁的实现

乐观锁的实现:

  • 一般乐观锁利用版本号或者CAS(比较并替换)算法来实现。
    • 版本号:是在数据表中添加一个version或者timestamp,在更新时对比用来和原数据进行笔记,如果一致则认为没有发生竞争,直接更新;如果不一致代表发生了竞争,需要重重新计算。
    • CAS算法:全称Compare And Swap,CAS是一个原子操作,依赖于底层的一条CPU指令。涉及到三个操作数
      • V:要更新的变量值
      • E:预期值 Expected
      • N:新值 New
      • 当且仅当 要更新的变量值的V和传入的预期值E相同才更新新值 N
  • Java中CAS的实现是依赖JDK提供的 Unsafe类来实现的,提供了三个本地方法
//对象的更新
    public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
//int类型的更新
    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
//long类型的更新
    public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
  • 大部分的CAS的实现都是利用循环来处理,当CAS失败时,重试到成功;如果并发太大,重试的次数非常多会导致大量的CPU消耗,所以一般是会加上一个次数限制,防止无休止的重试,消耗资源。
    • 存在的问题:
      • ABA问题:如果要求严格,则可以添加版本号/时间戳杜绝ABA问题
      • 大量循环消耗资源
      • 只能保证一个变量的原子操作,如果Object是一个组合对象,则对象内部的对象操作不能保证原子;可以使用AtomicReference保证组合对象的原子性。
  • CAS的实现类:
    • AtomicInteger
    • AtomicLong
    • AtomicReference

悲观锁:

  • 一般悲观锁利用 synchronized 关键字和 Lock对象来保证读写、写写的互斥,保证共享资源同一时间只有一个线程可以操作。
  • 在并发量不大,且无特殊锁获取,加解锁的需求是,建议直接使用 synchronized关键字,经JDK优化后synchronized不再像之前一样是一个觉得排它锁;锁升级可以满足大部分的并发的业务场景。(锁升级的原理可以看 【多线程】synchronized关键字详解
  • 悲观锁
    • synchronized
    • ReentrantLock
posted @ 2024-01-07 09:06  此木|西贝  阅读(26)  评论(0)    收藏  举报