1、乐观锁 VS 悲观锁

乐观锁与悲观锁是一种广义上的概念,体现了看待线程同步的不同角度。在Java和数据库中都有此概念对应的实际应用。

1.1 概念悲观锁:对于同一个数据的并发操作,悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。

乐观锁:乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作(例如报错或者自动重试)。

乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法。Java原子类中的递增操作就通过CAS自旋实现的。

1.2 Java中使用悲观锁:Java中,synchronized关键字和Lock的实现类都是悲观锁。

乐观锁:Java原子类中的递增操作就通过CAS自旋实现的。

1.3 Mysql中使用悲观锁:手动提交事务,select ... for update;

要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作后,MySQL会立刻将结果进行提交。

//我们可以使用命令设置MySQL为非autocommit模式:

set autocommit=0;

//设置完autocommit后,我们就可以执行我们的正常业务了。具体如下:

//0.开始事务

begin;/begin work;/start transaction; (三者选一就可以)

//1.查询出商品信息,锁定这行数据

select status from t_goods where id=1 for update;

//2.根据商品信息生成订单

insert into t_orders (id,goods_id) values (null,1);

//3.修改商品status为2

update t_goods set status=2;

//4.提交事务

commit;/commit work;乐观锁:使用版本号

update tablexxx set name=#name#,version=version+1 where id=#id# and version=#version

id字段要求是主键或者唯一索引,这样的话就是行锁,不然的话就是是锁表,会死人的

1.4 总结

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

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

2、公平锁 VS 非公平锁

2.1 概念公平锁:公平锁是指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。

image非公平锁:非公平锁是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获取锁的场景。非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。

2.2 Java中使用

对于Java ReetrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。

对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。

3、可重入锁 VS 非可重入锁

3.1 概念

可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。可重入锁的一个优点是可一定程度避免死锁。

public class Widget {

public synchronized void doSomething() {

System.out.println("方法1执行...");

doOthers();

}

public synchronized void doOthers() {

System.out.println("方法2执行...");

}

}

在上面的代码中,类中的两个方法都是被内置锁synchronized修饰的,doSomething()方法中调用doOthers()方法。因为内置锁是可重入的,所以同一个线程在调用doOthers()时可以直接获得当前对象的锁,进入doOthers()进行操作。

如果是一个不可重入锁,那么当前线程在调用doOthers()之前需要将执行doSomething()时获取当前对象的锁释放掉,实际上该对象锁已被当前线程所持有,且无法释放。所以此时会出现死锁。

3.2 Java中使用

ReentrantLock和synchronized都是重入锁

3.3 ReentrantLock实现原理

ReentrantLock继承父类AQS,其父类AQS中维护了一个同步状态state来计数重入次数,state初始值为0。获取锁

当线程尝试获取锁时,可重入锁先尝试获取并更新state值,如果state== 0表示没有其他线程在执行同步代码,则把state置为1,当前线程开始执行。

如果state!= 0,则判断当前线程是否是获取到这个锁的线程,如果是的话执行state+1,且当前线程可以再次获取锁。

释放锁

释放锁时,可重入锁先获取当前state的值,在当前线程是持有锁的线程的前提下,如果state-1 == 0,则表示当前线程所有重复获取锁的操作都已经执行完毕,然后该线程才会真正释放锁。

4、独享锁/排他锁/互斥锁 VS 共享锁/读写锁

独享锁和共享锁同样是一种概念。

4.1 概念独享锁:独享锁也叫排他锁、互斥锁,是指该锁一次只能被一个线程所持有。如果线程T对数据A加上排它锁后,则其他线程不能再对A加任何类型的锁。获得排它锁的线程即能读数据又能修改数据。

共享锁:共享锁是指该锁可被多个线程所持有。如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁。获得共享锁的线程只能读数据,不能修改数据。

4.2 Java中使用

JDK中的synchronized和JUC中Lock的实现类就是互斥锁;

JDK中的ReentrantReadWriteLock 是共享锁;

4.3 共享锁 ReentrantReadWriteLock

ReentrantReadWriteLock中有两把锁:ReadLock和WriteLock,由词知意,一个读锁一个写锁,合称“读写锁”。再进一步观察可以发现ReadLock和WriteLock是靠内部类Sync实现的锁。Sync是AQS的一个子类,这种结构在CountDownLatch、ReentrantLock、Semaphore里面也都存在。

在ReentrantReadWriteLock里面,读锁和写锁的锁主体都是Sync,但读锁和写锁的加锁方式不一样。读锁是共享锁,写锁是独享锁。读锁的共享锁可保证并发读非常高效,而读写、写读、写写的过程互斥,因为读锁和写锁是分离的。所以ReentrantReadWriteLock的并发性相比一般的互斥锁有了很大提升。

5、分段锁

5.1 概念

分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。

我们以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7和JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。

当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在哪一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。

但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。

分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。

5.2 Java中使用

ConcurrentHashMap

6、CAS

CAS(Compare and Swap 比较并交换)是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其他线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

CAS操作中包含三个操作数——需要读写的内存位置(V)、进行比较的预期原值(A)和拟写入的新值(B)。如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置值更新为新值B,否则处理器不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值(在CAS的一些特殊情况下将仅返回CAS是否成功,而不提取当前值)。CAS有效地说明了“我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可”。这其实和乐观锁的冲突检查+数据更新的原理是一样的。

CAS虽然很高效,但是它也存在三大问题,这里也简单说一下:ABA问题。CAS需要在操作值的时候检查内存值是否发生变化,没有发生变化才会更新内存值。但是如果内存值原来是A,后来变成了B,然后又变成了A,那么CAS进行检查时会发现值没有发生变化,但是实际上是有变化的。ABA问题的解决思路就是在变量前面添加版本号,每次变量更新的时候都把版本号加一,这样变化过程就从“A-B-A”变成了“1A-2B-3A”。

JDK从1.5开始提供了AtomicStampedReference类来解决ABA问题,具体操作封装在compareAndSet()中。compareAndSet()首先检查当前引用和当前标志与预期引用和预期标志是否相等,如果都相等,则以原子方式将引用值和标志的值设置为给定的更新值。

循环时间长开销大。CAS操作如果长时间不成功,会导致其一直自旋,给CPU带来非常大的开销。

只能保证一个共享变量的原子操作。对一个共享变量执行操作时,CAS能够保证原子操作,但是对多个共享变量操作时,CAS是无法保证操作的原子性的。

Java从1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作。

7、 AQS

AbstractQueuedSynchronizer 抽象队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch…

image

AQS维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。

state的访问方式有三种:

getState()

setState()

compareAndSetState()

AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。

不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。

tryAquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。

tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。

tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。

tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其他线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多少次,这样才能保证state是能回到零态的。

再以CountDownLatch为例,任务分为N个子线程去执行,state为初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会await()函数返回,继续后余动作。

一般来说,自定义同步器要么是独占方式,要么是共享方式,他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。

8、synchronized VS ReentrantLock

相同点:都是悲观锁

默认都是非公平锁,但是ReentrantLock可以通过参数构造公平锁

都是可重入锁

不同点:1.synchronized是在JVM层面实现的,因此系统可以监控锁的释放与否;而ReentrantLock是使用代码实现的,系统无法自动释放锁,需要在代码中的finally子句中显式释放锁lock.unlock()。在并发量比较小的情况下,使用synchronized是个不错的选择;但是在并发量比较高的情况下,其性能下降会很严重,此时ReentrantLock是个不错的方案。

在使用synchronized 代码块时,可以与wait()、notify()、nitifyAll()一起使用,从而进一步实现线程的通信。

其中,wait()方法会释放占有的对象锁,当前线程进入等待池,释放cpu,而其他正在等待的线程即可抢占此锁,获得锁的线程即可运行程序;线程的sleep()方法则表示,当前线程会休眠一段时间,休眠期间,会暂时释放cpu,但并不释放对象锁,也就是说,在休眠期间,其他线程依然无法进入被同步保护的代码内部,当前线程休眠结束时,会重新获得cpu执行权,从而执行被同步保护的代码。

wait()和sleep()最大的不同在于wait()会释放对象锁,而sleep()不会释放对象锁。

notify()方法会唤醒因为调用对象的wait()而处于等待状态的线程,从而使得该线程有机会获取对象锁。调用notify()后,当前线程并不会立即释放锁,而是继续执行当前代码,直到synchronized中的代码全部执行完毕,才会释放对象锁。JVM会在等待的线程中调度一个线程去获得对象锁,执行代码。

需要注意的是,wait()和notify()必须在synchronized代码块中调用。

notifyAll()是唤醒所有等待的线程

作者:张凯_9908

链接:https://www.jianshu.com/p/70cef5acfd91

来源:简书

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

mysql原子性和乐观锁_乐观锁 VS 悲观锁相关推荐

  1. 悲观锁和乐观锁_浅谈数据库悲观锁和乐观锁

    现在我们简单聊一下数据库中的悲观锁和乐观锁. 悲观锁 悲观锁正如其名称,比较悲观.总会认为:每当修改数据时,会有其他线程也会同时修改该数据.所以针对这种情况悲观锁的做法是:读取数据之后就加锁(eg: ...

  2. mysql事务处理是悲观锁还是_数据库事务的悲观锁和乐观锁

    转载出处:http://www.hollischuang.com/archives/934 在数据库的锁机制中介绍过,数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数 ...

  3. mysql悲观锁和乐观优缺点_乐观锁、悲观锁和MVCC各是什么?各自优缺点是什么?...

    在数据库的实际使用过程中,我们常常会遇到不希望数据被同时写或者读的情景,例如秒杀场景下,两个请求同时读到系统还有库存1个,然后又先后把库存更新为0,这时候就会出现超卖的情况,这时候货物的实际库存和我们 ...

  4. 【Java锁体系】一、悲观锁和乐观锁

    目录: [Java锁体系]一.悲观锁和乐观锁 [Java锁体系]二.Java中的CAS机制算法 [Java锁体系]三.自旋锁详解 [Java锁体系]四.Synchronized关键字详解 [Java锁 ...

  5. mysql mybaits 悲观锁_乐观锁、悲观锁,这一篇就够了!

    1. 乐观锁 乐观锁顾名思义就是在操作时很乐观,认为操作不会产生并发问题(不会有其他线程对数据进行修改),因此不会上锁.但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或C ...

  6. 悲观锁和乐观锁_乐观锁和悲观锁 以及 乐观锁的一种实现方式-CAS

    悲观锁 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞知道它拿到锁.传统的关系型数据库里面就用到了很多的这种锁机制,比如行锁,表锁等 ...

  7. 关抢占 自旋锁_互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景

    前言 生活中用到的锁,用途都比较简单粗暴,上锁基本是为了防止外人进来.电动车被偷等等. 但生活中也不是没有 BUG 的,比如加锁的电动车在「广西 - 窃·格瓦拉」面前,锁就是形同虚设,只要他愿意,他就 ...

  8. 轻量级锁_一句话撸完重量级锁、自旋锁、轻量级锁、偏向锁、悲观、乐观锁等各种锁 不看后悔系列...

    重量级锁?自旋锁?自适应自旋锁?轻量级锁?偏向锁?悲观锁?乐观锁?执行一个方法咋这么辛苦,到处都是锁. 今天这篇文章,给大家普及下这些锁究竟是啥,他们的由来,他们之间有啥关系,有啥区别. 重量级锁 如 ...

  9. mysql锁总结知乎_Mysql悲观锁乐观锁区别与使用场景

    概念上区别 乐观锁(Optimistic Locking):顾名思义,对加锁持有一种乐观的态度,即先进行业务操作,不到最后一步不进行加锁,"乐观"的认为加锁一定会成功的,在最后一步 ...

最新文章

  1. 用 easy-json-schema 代替 json-schema 吧
  2. 从jQuery 入口方式写jQuery工具类库
  3. spring中的IOC和AOP
  4. P3224 [HNOI2012]永无乡(并查集+权值线段树合并/平衡树)
  5. openmv串口数据 串口助手_STM32 串口接收不定长数据 STM32 USART空闲检测中断
  6. 前端学习(2864):简单秒杀系统学习之优化js
  7. 300张小抄表搞定机器学习知识点:学习根本停不下来!
  8. HDU 5752 Sqrt Bo【枚举,大水题】
  9. pycharm——常用快捷键操作
  10. 定时器开始时延时了十几秒_第六章--系统滴答定时器
  11. 小代码编写神器:LINQPad 使用入门
  12. 什么是领域模型(domain model)?贫血模型(anaemic domain model) 和充血模型(rich domain model)有什么区别...
  13. VFP开眼看世界的第一眼,就是学会真正的BS开发,走错一步费三年
  14. FTP客户端搭建(linux环境)
  15. JSP javaweb餐厅点餐系统源码(点餐系统)jsp点餐系统网上订餐
  16. mysql 纯真ip 导出dat_用纯真ip数据库.dat文件查询ip归属
  17. 利用集成支持向量机模型评估信贷风险
  18. 设计编程网站集,调色板,软件下载
  19. 如何用SCRM销售管理系统管理销售和做销售管理
  20. 邮件中的CC和BCC

热门文章

  1. 计算机专业英语critical,计算机专业英语教程汇总.ppt
  2. CCF201812-1 小明上学
  3. PostgreSQL的pg_basebackup备份恢复详解
  4. MySQL在其版本迭代后Online DDL功能发生了哪些变化?
  5. 国庆前的最后3场直播活动!!
  6. 400+节点的Elasticsearch集群运维
  7. 深入解析:你听说过Oracle数据库的更新重启动吗?
  8. 昇腾CANN论文上榜CVPR,全景图像生成算法交互性再增强
  9. 【华为云技术分享】LiteOS无法直接烧录或者烧录失败解决方法--ST-Link
  10. 将Android手机打造成你的Python开发者桌面#华为云·寻找黑马程序员#