锁机制介绍

悲观锁

共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程,会对资源加锁,可能出现死锁的情况。

乐观锁

共享资源不会加锁,线程可以不停地执行访问资源的代码,无需加锁也无需等待,只是在提交修改的时候去验证对应的资源(也就是数据)是否被其它线程修改了(具体方法可以使用版本号机制或 CAS 算法)。

注意:悲观锁通常用于写比较多的场景,而乐观锁通常用于写比较少的场景。

实现乐观锁的两种方式

1. 版本号机制

        在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。当数据被修改时,version 值会加一。线程 A 要更新数据值时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值为当前数据库中的 version 值相等时才更新,避免更新过程中有其他线程更新了数据导致出现数据覆盖的情况。否则重试更新操作,直到更新成功。

2.CAS(Compare And Swap)算法

        CAS用一个预期值和要更新的变量值进行比较,两值相等才会对数据进行更新。CAS 是一个原子操作,底层依赖于一条 CPU 的原子指令。CAS 涉及到三个操作数,当且仅当 V(要更新的变量值) 的值等于 E (预期值)时,CAS 通过原子方式用N (新值)来更新 V 的值。如果不等,说明已经有其它线程更新了 V,则当前线程放弃更新。当多个线程同时使用 CAS 操作一个变量时,只有一个会胜出,并成功更新,其余均会失败,但失败的线程并不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。

乐观锁使用CAS算法实现存在的问题

 1. ABA问题(V=E时并不能说明数据没有被其他线程更新,可能是被改为其他值,又修改回原值)

2. 时间开销大。CAS算法经常用到自旋操作进行重试,给CPU带来很大的执行开销

3. CAS只能保证对单个共享变量有效

synchronized关键字

        synchronized关键字解决的是多个线程之间访问资源的同步性,可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。在 Java 早期版本中,synchronized 属于 重量级锁,效率低下。在 Java 6 之后, synchronized 引入了大量的优化。

synchronized 关键字的使用方式

类别代码加锁目标
修饰实例方法 
synchronized void method() {
    //业务代码
}
当前对象实例
修饰实例方法 
synchronized static void method() {
    //业务代码
}
当前类
修饰代码块 (锁指定对象/类)
synchronized(this) {
    //业务代码
}
指定对象object/类 类.class 

注意:构造方法不能使用 synchronized 关键字修饰,其本身就是线程安全的

synchronized 底层原理

1. synchronized 同步代码块

        在字节码文件中synchronized 同步语句块的实现使用的是 monitorentermonitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。字节码中包含一个 monitorenter 指令以及两个 monitorexit 指令,两个 monitorexit 指令是为了保证锁在同步代码块代码正常执行以及出现异常的这两种情况下都能被正确释放。

        执行monitorenter时,会尝试获取对象的锁,如果锁的计数器为 0 则表示锁可以被获取,获取后将锁计数器设为 1。对象锁的的拥有者线程才可以执行 monitorexit 指令来释放锁。在执行 monitorexit 指令后,将锁计数器设为 0,表明锁被释放,其他线程可以尝试获取锁。

2. synchronized 同步方法

  synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。

两种同步方法的本质都是限制对锁的获取。

synchronized和volatile 有什么区别?

1. volatile 关键字是线程同步的轻量级实现,性能更佳

2. volatile 关键字只能用于变量而 synchronized 关键字可以修饰方法以及代码块 

3. volatile关键字用于解决变量在多个线程之间的可见性,而 synchronized 关键字解决的是多个线程之间访问资源的同步性,两者并不是对立的

4. volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证

synchronized和ReentrantLock 有什么区别?

1. synchronized 依赖于 JVM 实现,并没有直接暴露给用户

2. ReentrantLock依赖于 API 实现,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成

3. ReentrantLock 比 synchronized 增加了一些高级功能(等待可中断、公平锁、选择性通知)

公平锁与非公平锁的区别?

公平锁:先申请的线程先得到锁,线程上下文切换更频繁,性能较差。

非公平锁: 随机或者按照优先级排序获取锁的顺序,性能更好但可能出现有些线程永远无法获取到锁的情况。

可重入锁是什么?

        可重入锁 也叫递归锁,指的是线程可以再次获取自己的内部锁。即一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取。

读写锁

1. 读锁是共享锁,写锁是独占锁

2. 线程在持有读锁时,不能同时持有写锁,不论读锁是否被当前线程持有

3. 线程在持有写锁时,当前线程可以持有读锁

4. 写锁可以降级未读锁,但读锁不能升级为写锁,读锁升级会引起线程争夺,还可能导致死锁问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值