Java之并发编程(四)

六、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自旋锁总是会执行,且自动调整自旋次数,无法手动开启关闭。

链接:
Java线程并发中常见的锁–自旋锁 偏向锁


3.Synchronized、ReentrantLock

3.1 Synchronized

3.1.1 概念

  • 关键字,依赖于 JVM,解决多个线程间访问资源同步性问题,保证其修饰的方法或代码块任意时刻只能有一个线程访问

  • 可以把任意非NULL的对象当做锁,属于重量级锁、独占式悲观锁、可重入锁、隐式锁、非公平锁

3.1.2 3种主要使用方式

  1. 修饰实例方法(锁当前对象实例)
    • 给当前对象实例加锁,进入同步代码前要获得当前对象实例的锁 。

  1. 修饰静态方法(锁当前类)
    • 给当前类加锁,会作用于类的所有对象实例 ,进入同步代码前要获得 当前 class 的锁
    • 这是因为静态成员不属于任何一个实例对象,归整个类所有,不依赖于类的特定实例,被类的所有实例共享

  1. 修饰代码块(锁指定对象/类)
    • 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内容分发网络,是集群页面缓存服务器,尽早返回用户需要的数据,加速用户访问速度,也减轻后端服务器的负载压力;


上一篇跳转—Java之并发编程(三)


本篇文章主要参考链接如下:

参考链接-Java常见锁 【超全面】


持续更新中…

随心所往,看见未来。Follow your heart,see light!

欢迎点赞、关注、留言,一起学习、交流!

你可能感兴趣的:(后端Java,开发学习拓展,java,高并发,Java锁,并发编程)