【多线程】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关键字详解) - 悲观锁
synchronizedReentrantLock

浙公网安备 33010602011771号