JAVA 锁

32 阅读4分钟

前言

compareAndSwap(比较并更新),是一种在多线程环境下实现同步功能的机制

简述就是,从某一内存上取值V,和预期值A进行比较,如果内存值V和预期值A的结果相等,那么我就把新值B更新到内存,如果不相等,那么就重复上述操作直到成功为止。

诞生背景

synchronized 时代:多线程中为了保持数据的准确性,避免多个线程同时操作某个变量,很多情况下利用synchronied 关键字实现同步锁。这种操作思路,是让其他线程等待,是一种悲观策略,认为线程会修改数据,所以开发就把持有锁的线程锁住,其他线程只能挂起等待,等待锁的释放【效率低】。

volatile 时代:同步锁带来了线程执行时候之间阻塞,而valotile 这种非阻塞机制,在多线程竞争同一个数据时候不会发生阻塞的情况,效率高。

valatile 保证了线程之间的可见性和程序执行的有效性,但是它不能保证原子性(i++)

由此出现了CAS解决了volatile不能保证原子性问题。

ABA问题

Unsafe 类

提供了硬件级别的原子性操作,Unsafe类中的方法都是native方法,它们使用JNI的方式访问本地C++实现库。

暂略

多线程锁

悲观锁:认为数据会被修改,所以它的机制就是多线程下只有一个线程能够获取到锁,其他线程等待锁的获取。 乐观锁:相对悲观锁而言的,它认为数据在一般情况下不会修改,所以在访问数据时不会加锁,它只是在修改数据是锁定数据的状态(保证同一时刻只有一个线程能修改成功(比如数据库的版本号或者业务状态,CAS 就是一种乐观锁实现)。

悲观锁的一些局限问题

在多线程竞争下,加锁和释放锁会导致比较多的上下文切换的,性能开销比较大的 一个线程持有锁,其他线程就必须挂起等待 一个优先级比较高的线程等待一个优先级比较低的线程释放锁,是会引起性能问题

公平锁和非公平锁

这样分类的依据其实是根据获取锁的抢占机制来区分。 公平锁:是按照获取锁的请求的先后来决定,最早请求锁的线程,将先获取锁 非公平锁:是根据线程运行状态,来决定谁先获取锁的

独占锁与共享锁

这个分类是根据锁被一个线程拥有还是多个线程拥有

独占锁:多线程下,只有一个线程拥有锁(ReentrantLock、synchronized) 共享锁:同时由多个线程持有,如:ReadWriteLock 读写锁

自旋锁

这个概念也是和操作系统线程切换的原理有关,因为java线程是和操作系统的线程相对应的。当一个线程获取锁时失败时,会切换到内核态被挂起。

当前去获取锁时,又会切换到内核态唤醒线程。这样的过程资源开销是很大的,一定程度上会影响性能的。

自旋锁的设计目的:就是当线程获取锁时,发现锁已经被占用时,它不会立马阻塞挂起,而是多次尝试重新获取(尝试次数JVM有参数可设置:-XX:preBlockSpinsh)

Lock 接口

java5之后出现Lock接口,lock提供(重入、公平等锁),可以在非阻塞结构的上下文(hand-over-hand 和锁重排算法)中使用这些规则。主要实现是ReentrantLock

  • synchronied 关键字是基于JVM层面实现的,JVM控制锁的获取和释放。Lock接口是JDK层面的,手动进行锁的获取和释放,灵活性更高。
  • 锁超时特性,synchronized 是不支持的
  • 锁中断,synchronized 不支持的
  • 锁的公平性:synchronied是无法设置的,它就是非公平锁。而Lock接口是可以设置的