六、Java 常见锁
1.乐观、悲观锁
1.1 乐观锁
- 读数据都认为别人不会修改所以不上锁,但是在更新时通过CAS实现
1.2 悲观锁
- 每次读写数据都会上锁进行阻塞
2.自旋锁
2.1 原理
- 如果持有锁的线程能在短时间内释放锁,等待锁的线程只需自旋(不需要进行用户线程和CPU内核切换,进入阻塞挂起而后唤醒[这里需要2个上下文切换过程])
- 等锁释放后就能立即获取锁,避免用户态内核态切换的消耗
- 但是如果一直获取不到锁,就会不断消耗CPU资源,所以需要设置最长等待锁的自旋时间,超过就进入阻塞状态。
2.2 适合场景
- 适合锁竞争不激烈且锁时间短;不适合锁竞争激烈且锁时间长
2.3设置
2.3.1 自旋锁时间阈值
- 1.5固定;1.6引入了适应性自旋锁,根据前一次同一个锁的自旋时间及锁拥有线程的状态决定,默认最佳时间是1个线程上下文切换时间
2.3.2 开启
- 1.6 XX:-UseSpinning;1.7后JVM自旋锁总是会执行,且自动调整自旋次数,无法手动开启关闭。
3.Synchronized、ReentrantLock
3.1 Synchronized
3.1.1 概念
-
关键字,依赖于 JVM,解决多个线程间访问资源同步性问题,保证其修饰的方法或代码块任意时刻只能有一个线程访问
-
可以把任意非NULL的对象当做锁,属于重量级锁、独占式悲观锁、可重入锁、隐式锁、非公平锁
3.1.2 3种主要使用方式
- 修饰实例方法(锁当前对象实例)
- 给当前对象实例加锁,进入同步代码前要获得当前对象实例的锁 。
- 修饰静态方法(锁当前类)
- 给当前类加锁,会作用于类的所有对象实例 ,进入同步代码前要获得 当前 class 的锁
- 这是因为静态成员不属于任何一个实例对象,归整个类所有,不依赖于类的特定实例,被类的所有实例共享
- 修饰代码块(锁指定对象/类)
- synchronized(object) 表示进入同步代码块前要获得给定对象的锁。
- synchronized(类.class) 表示进入同步代码前要获得给定 Class的锁
备注:
- 如果线程A调用一个实例对象的静态synchronized方法,而线程B同时调用这个实例对象所属类的非静态synchronized方法是允许的,并不互斥!
- 因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。
3.1.3 实现
-
重量级锁:需要调用操作系统相关接口,给线程加锁消耗时间长
-
1.6及之后对锁优化:自适应锁、锁消除、锁粗化、轻量级锁、偏向锁
3.2 ReentrantLock
- 依赖于 JDK的API,继承接口Lock并实现了接口中定义的方法。除能实现synchronized的功能,还提供了可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法,另外还有可选择通知、多个锁。
- 属于轻量级锁、乐观锁、可重入锁、显式锁、实现公平非公平锁(默认非公平锁)
4.公平锁、非公平锁
4.1 公平锁
- 按照锁的请求顺序获取锁
4.2 非公平锁
- 不能保证按照锁的请求顺序获取锁(如JVM随机就近原则分配锁)
- 比公平锁性能高5~10倍,ReentrantLock默认、synchronized都属于
5.可重入锁
- 也称递归锁,同一线程外层函数获得锁之后 ,内层递归函数仍然可以获取该锁,ReentrantLock和synchronized都属于
6.读、写锁
ReadWriteLock、ReentrantReadWriteLock
6.1 读锁
- 读即查询数据使用读锁,多个读锁不互斥,读锁与写锁互斥
6.2 写锁
- 写即修改数据使用写锁,只能有一个人写,且不能同时读取
7.同步锁、死锁
7.1 同步锁
- 同一时间只允许一个线程访问共享数据,synchronized可以获取一个对象的同步锁
2.2 死锁
- 两个或以上线程(或进程)执行过程中,因为争夺资源造成互相等待形成死锁
8.按锁状态分类
8.1 重量级锁
- 通过对象内部的监视器锁monitor实现,而monitor依赖于操作系统Mutex Lock锁实现
- 操作系统实现线程切换需要用户态和和心态切换,时间长
8.2 轻量级锁
- 相对于重量级锁而言。在没有多线程竞争前提下,减少重量级锁产生的消耗
- 适用线程交替执行同步块场景,如果存在同一时间访问同一个锁情况则膨胀为重量级锁
8.3 偏向锁
- 相对于轻量级锁而言。在没有多线程竞争情况下尽量减少不必要的轻量级锁执行路径
- 因为轻量级锁的获取及释放要依赖多次的CAS原子指令,而偏向锁只需要在置换ThreadId的时候依赖一次CAS原子指令
- 适用于只有一个线程执行同步块场景,进一步提高性能。单如果存在多线程竞争的情况则必须撤销偏向锁。
8.4 无锁状态
备注:锁升级
- 锁的竞争造成锁升级,只能单向从低到高
9. 分段锁
- 只对所操作的段加锁,而不影响线程对其他段的访问,如ConcurrentHashMap
10.显式、隐式锁
10.1 显式锁
- 使用者需要手动写代码去获取锁和释放锁,如ReentrantLock
10.2 隐式锁
- 使用者不需要手动写代码去获取锁和释放锁,加锁和释放锁过程也不会显示,如synchronized
六、Java锁优化
1.减少锁持有时间
- 只用在有线程安全要求的程序上加锁,且尽量减少锁持有时间
2.降低锁粒度
- 包括分段锁,将大对象(可能被很多线程访问)拆成小对象,增加并行度,降低锁竞争,增加偏向锁、轻量级锁的占比数量,如ConcurrentHashMap
3.锁分离
- 将功能分离成读锁与写锁,如ReadWriteLock
- 分离思想还可以延伸,如LinkedBlockingQueue根据结构从头部读取数据,从尾部存放数据
4.锁粗化
- 减少锁持有时间的逆向,如果其他线程对该资源的请求频率很低,而该线程需要对该资源频繁访问修改,则可以延长锁持有时间,减少对该锁不停请求、同步、释放操作,进而降低系统资源消耗
链接:
Java锁优化思路及JVM实现
XX、高并发
21.高并发解决方案
1.1 应用程序和静态资源文件分离
同 #### 1.2 页面静态化技术
1.2 页面缓存
可以使用Ngnix提供缓存功能、或者页面缓存服务器Squid
1.3 集群与分布式
1.4 反向代理
1.5 CDN
CDN内容分发网络,是集群页面缓存服务器,尽早返回用户需要的数据,加速用户访问速度,也减轻后端服务器的负载压力;
本篇文章主要参考链接如下:
持续更新中…
随心所往,看见未来。Follow your heart,see light!
欢迎点赞、关注、留言,一起学习、交流!