一.前言

最近项目遇到多线程并发的情景(并发抢单&恢复库存并行),代码在正常情况下运行没有什么问题,在高并发压测下会出现:库存超发/总库存与sku库存对不上等各种问题。

在运用了 限流/加锁等方案后,问题得到解决。

加锁方案见下文。

二.乐观锁 & 悲观锁

1.乐观锁

顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号(version)等机制。

例如:mybatis-plus 自带插件OptimisticLockerInterceptor,在数据库表加上一个version字段,每次更新完数据库mp会自动在version字段上加1,如果在更新提交的时候发现version字段的值与数据库中最新的值不一致,则提交失败。

2.悲观锁

悲观锁总是假设会出现最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。

传统的MySQL关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

Java的同步synchronized关键字的实现就是典型的悲观锁。

3.总结

悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。

乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。

三.独占锁 & 共享锁

1.独占锁

是指该锁一次只能被一个线程所持有。

例如:Synchronized 和 ReentrantLock ,它们同时也是悲观锁

2.共享锁

是指该锁可被多个线程所持有,允许多个线程同时去获取。

例如:Semaphore 和 ReadWriteLock 和 countdownlatch,其读锁是共享锁,写锁是独享锁。

3.总结

读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的。

独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。

四.公平锁 & 非公平锁

1.公平锁

加锁前先查看是否有排队等待的线程,有的话优先处理排在前面的线程,先来先得。

2.非公平锁

线程加锁时直接尝试获取锁,获取不到就自动到队尾等待。

3.总结

更多的是直接使用非公平锁:非公平锁比公平锁性能高5-10倍,因为公平锁需要在多核情况下维护一个队列,如果当前线程不是队列的第一个无法获取锁,增加了线程切换次数。

五.java线程锁

由于多个线程是共同占有所属进程的资源和地址空间的,那么就会存在一个问题:如果多个线程要同时访问某个资源,怎么处理?

在Java并发编程中,经常遇到多个线程访问同一个共享资源 ,这时候作为开发者必须考虑如何维护数据一致性,这就是Java锁机制(同步问题)的来源。

Java提供了多种多线程锁机制的实现方式,常见的有:

synchronized

ReentrantLock

Semaphore

AtomicInteger等

每种机制都有优缺点与各自的适用场景,必须熟练掌握他们的特点才能在Java多线程应用开发时得心应手。

1.Synchronized

在Java中synchronized关键字被常用于维护数据一致性。synchronized机制是给共享资源上锁,只有拿到锁的线程才可以访问共享资源,这样就可以强制使得对共享资源的访问都是顺序的。

Java开发人员都认识synchronized,使用它来实现多线程的同步操作是非常简单的,只要在需要同步的对方的方法、类或代码块中加入该关键字,它能够保证在同一个时刻最多只有一个线程执行同一个对象的同步代码,可保证修饰的代码在执行过程中不会被其他线程干扰。使用synchronized修饰的代码具有原子性和可见性,在需要进程同步的程序中使用的频率非常高,可以满足一般的进程同步要求。

synchronized (obj) {

//方法

…….

}

synchronized实现的机理依赖于软件层面上的JVM,因此其性能会随着Java版本的不断升级而提高。

到了Java1.6,synchronized进行了很多的优化,有适应自旋、锁消除、锁粗化、轻量级锁及偏向锁等,效率有了本质上的提高。在之后推出的Java1.7与1.8中,均对该关键字的实现机理做了优化。

需要说明的是,当线程通过synchronized等待锁时是不能被Thread.interrupt()中断的,因此程序设计时必须检查确保合理,否则可能会造成线程死锁的尴尬境地。

最后,尽管Java实现的锁机制有很多种,并且有些锁机制性能也比synchronized高,但还是强烈推荐在多线程应用程序中使用该关键字,因为实现方便,后续工作由JVM来完成,可靠性高。只有在确定锁机制是当前多线程程序的性能瓶颈时,才考虑使用其他机制,如ReentrantLock等。

总结:在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronize,另外可读性非常好。

2.ReentrantLock

可重入锁,顾名思义,这个锁可以被线程多次重复进入进行获取操作。

ReentantLock继承接口Lock并实现了接口中定义的方法,除了能完成synchronized所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的法。

Lock实现的机理依赖于特殊的CPU指定,可以认为不受JVM的约束,并可以通过其他语言平台来完成底层的实现。在并发量较小的多线程应用程序中,ReentrantLock与synchronized性能相差无几,但在高并发量的条件下,synchronized性能会迅速下降几十倍,而ReentrantLock的性能却能依然维持一个水准。

因此我们建议在高并发量情况下使用ReentrantLock。

ReentrantLock引入两个概念:公平锁与非公平锁。

公平锁指的是锁的分配机制是公平的,通常先对锁提出获取请求的线程会先被分配到锁。反之,JVM按随机、就近原则分配锁的机制则称为不公平锁。

ReentrantLock在构造函数中提供了是否公平锁的初始化方式,默认为非公平锁。这是因为,非公平锁实际执行的效率要远远超出公平锁,除非程序有特殊需要,否则最常用非公平锁的分配机制。

ReentrantLock通过方法lock()与unlock()来进行加锁与解锁操作,与synchronized会被JVM自动解锁机制不同,ReentrantLock加锁后需要手动进行解锁。为了避免程序出现异常而无法正常解锁的情况,使用ReentrantLock必须在finally控制块中进行解锁操作。通常使用方式如下所示:

/**

* 初始化一个非公平锁(该锁只适用于单实例)

*/

private static Lock lock = new ReentrantLock(false);

void test() {

try {

lock.lock();

//...执行业务逻辑

}finally {

lock.unlock();

}

}

总结:在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍,而ReentrantLock确还能维持常态。高并发量情况下使用ReentrantLock。

3.Semaphore (信号量)

上述两种锁机制类型都是“互斥锁”,互斥是进程同步关系的一种特殊情况,相当于只存在一个临界资源,因此同时最多只能给一个线程提供服务。但是,在实际复杂的多线程应用程序中,可能存在多个临界资源,这时候我们可以借助Semaphore信号量来完成多个临界资源的访问。

Semaphore基本能完成ReentrantLock的所有工作,使用方法也与之类似,通过acquire()与release()方法来获得和释放临界资源。

经实测,Semaphone.acquire()方法默认为可响应中断锁,与ReentrantLock.lockInterruptibly()作用效果一致,也就是说在等待临界资源的过程中可以被Thread.interrupt()方法中断。

此外,Semaphore也实现了可轮询的锁请求与定时锁的功能,除了方法名tryAcquire与tryLock不同,其使用方法与ReentrantLock几乎一致。Semaphore也提供了公平与非公平锁的机制,也可在构造函数中进行设定。

Semaphore的锁释放操作也由手动进行,因此与ReentrantLock一样,为避免线程因抛出异常而无法正常释放锁的情况发生,释放锁的操作也必须在finally代码块中完成。

/** 定义一个非公平的共享锁 */

public static Semaphore LOCK = new Semaphore(5, false);

void test() {

try{

//获取许可

LOCK.acquire();

}

finally {

//释放许可

LOCK.release();

}

}

总结:Semaphore有着非常强大的功能,并且是共享锁,在特殊情景时非常有效。

java线程钥匙_Java多线程并发编程/锁的理解相关推荐

  1. java线程池_Java多线程并发:线程基本方法+线程池原理+阻塞队列原理技术分享...

    线程基本方法有哪些? 线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等. 线程等待(wait) 调用该方法的线程进入 WAITING 状态,只有等 ...

  2. java线程栅栏_Java多线程并发系列之闭锁(Latch)和栅栏(CyclicBarrier)

    今天项目上遇到一个多线程任务问题,大概图文描述一下: 1.前端需要及时返回任务状态 2.后台开了一个任务线程去执行具体的业务,业务包括四个部分,四个部分全部完成才算完成 3.业务中某些耗时的或者需要多 ...

  3. 网易云课堂微专业--Java高级开发工程师--多线程并发编程--学习笔记(二)

    文章目录 第一章 多线程并发编程 第二节 线程安全问题 1.2.1 线程安全之可见性问题 多线程中的问题 从内存结构到内存模型 工作内存缓存 指令重排序 内存模型的含义 Shared Variable ...

  4. java线程释放_Java多线程出现异常会自动释放锁

    Java多线程出现异常会自动释放锁 package com.wkcto.intrinsiclock; /** * 同步过程中线程出现异常, 会自动释放锁对象 * * Author: 老崔 */ pub ...

  5. java线程基础_Java多线程基础

    前言 在我们工作和学习的过程中,Java线程我们或多或少的都会用到,但是在使用的过程上并不是很顺利,会遇到各种各样的坑,这里我通过讲解Thread类中的核心方法,以求重点掌握以下关键技术点: 线程的启 ...

  6. java 线程 组成_java多线程

    一:基本知识点 1.1线程与进程区别: 1.进程是资源分配的最小单位,线程是CPU调度的最小单位 2.一个进程由一个或多个线程组成 3.进程之间相互独立,每个进程都有独立的代码和数据空间,但同一进程下 ...

  7. java 线程由浅入深_Java多线程:由浅入深看synchronized的底层实现原理

    Java多线程:由浅入深看synchronized的底层实现原理-1.jpg (91.72 KB, 下载次数: 0) 2018-9-21 03:55 上传 前言 前俩篇文章,我们聊了聊线程/进程的概念 ...

  8. java 线程钩子_高级并发编程系列六(线程池钩子函数)

    1.考考你 国庆假期快要结束了,准备回到工作岗位的你,是不是已经开始撸起袖子敲代码,反正发完文章我就要准备去加班了,程序员就这样,有干劲对吧 那么来吧,让我们一起分享完高级并发编程系列中,线程池小节的 ...

  9. java线程入门_java多线程快速入门(一)

    1.什么是进程 比如:QQ.QQ游戏.eclipse都是进程,可以通过任务管理器查看进程 2.进程和线程区别 线程是进程的一部分,一个进程可以包含多个线程,一个线程只能属于一个进程 进程是所有线程的集 ...

最新文章

  1. 有人要在「动物森友会」上开AI学术会议,我看你们就是在家想玩游戏吧
  2. 那一年,我考入了西北师范大学GIS专业,然而我很迷茫,GISer的职业规划到底是怎样的?
  3. 本地搭建wp,更新升级时需要ftp的解决办法
  4. 计算机英语讲课笔记01
  5. 【github系列】github上传空目录
  6. 推荐3款简约好用录屏工具
  7. x1电源灯一直闪_小器鬼LED平板灯 | 明亮好光,洁净好家
  8. Atitit.swt 线程调用ui控件的方法
  9. 计算机初级证单选题,计算机类软考初级软考程序员单选题
  10. Window ffmpeg 推摄像头音视频流到服务器
  11. linux系统网络老掉线,Linux使用ADSL上网时经常掉线
  12. [Leetcode] 158. Read N Characters Given Read4 II - Call multiple times 解题报告
  13. linux识别riser卡,Riser卡和PCIe槽位
  14. Java连接Sql Server的过程及遇到的问题(极端新手向)
  15. aiml的中文适配aiml_cn
  16. 一键全自动Typora备份印象笔记的工具
  17. Tyvj P1031 热浪
  18. E01 GBase 8a MPP Cluster V95 安装和卸载
  19. 升华思想境界,走出博士的专家路线
  20. Vue 2.0 官网 API 零碎知识整理

热门文章

  1. ifelse的命令空间
  2. LeetCode简单题之两个数对之间的最大乘积差
  3. RGB-D相机视觉SLAM
  4. 未来几年自动驾驶预测(上)
  5. 摄像头定位:ICCV2019论文解析
  6. 大数据调度平台Airflow(八):Airflow分布式集群搭建及测试
  7. centos7 安装Git
  8. TypeError: can only concatenate str (not “float“) to str
  9. Android Read-only file system
  10. inux 软件编译、安装、删除