
本文共 2622 字,大约阅读时间需要 8 分钟。
synchronized
重量级锁,完全自动化,不需要手动加锁和解锁,使用方便,但是容易造成死锁,
synchronized是非公平锁,拥有可重入性,
ReentrantLock / synchronized 可重入锁 / 递归锁
重入锁指的是在同一线程,在外层函数获得锁之后,内层递归函数仍然有获得锁的代码,但执行时不受影响,比如A方法加锁了,B方法也加锁了,A调用B方法,表面上看是有2个锁,但实际上他们用的都是同一把锁!
ReentrantLock 属于轻量级的锁
synchronized 和 ReentrantLock 都拥有可重入锁的特性,
volatile
通过在变量中添加volatile修饰符,在多线程中修改本地内存时,会强制将本地内存的值刷新到主内存;从而实现 线程的可见性和原子性,但某些博客说 volatile 不能实现原子性,只能实现可见性和有序性,为此我也懵;不管这个了,先来看看volatile的底层执行原理
重量级锁和轻量级锁的区别
重量级锁:完全自动化,不需要手动上锁和解锁;每次上锁都会将线程提交给操作系统的内核,在底层直接将锁交给操作系统的内核来管理,
轻量级锁:需要手动上锁和解锁,使用起来更加更加灵活,由java代码实现,本质上是自旋锁,底层用到了CAS(比较和交换)实现;
读写锁 ReetrantReadWriteLock
为了保证多个线程同时访问一个资源进行读写操作时不会出现脏读的情况,有必要使用读写锁进行限制,多个线程同时读一个资源时没有任何问题,但是读写同时进行时就会有问题,写写操作也会有问题,为了保证线程安全的情况下,就需要一个读写锁来解决这个问题;
接下来我们用2个线程模拟四种情况,让我们看看底层有哪些变化,其中AB代表2个不同的线程去读取同一个资源;
1、两个线程都读取数据 2、先读后写 3、先写后读 4、两个线程都写
公平锁
公平锁指的是多个线程按照指定的顺序执行,整个过程有序地进行每个线程,就像银行排队一样,需要办理业务的人们排成一排,有序地进行办理业务,上一个人业务办理完成后进行下一个人的业务办理!
-
优点:所有的线程都能得到资源,不会饿死在队列中。
-
缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。
非公平锁
非公平锁顾名思义,就是不公平的,多个线程通过抢占的方式进行执行,谁抢到就给谁执行,就跟强盗逻辑是一样的,谁抢到就是谁的;
-
优点:可以减少PU唤醒线程的开销,整体的吞吐效率会高点,PU也不必取唤醒所有线程,会减少唤起线程的数量。
-
缺点:你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死!
悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁,有任何一线程对数据进行读写操作都会上锁,数据库的行锁、表锁、读锁,写锁都是悲观锁,Java中synchronized
和ReentrantLock
等独占锁就是悲观锁思想的实现。
乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,有任何一线程对数据进行写操作都会上锁,;
乐观锁常见的两种实现方式
乐观锁一般会使用版本号机制或CAS算法实现。
1. 版本号机制
一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。举一个简单的例子:
假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。当需要对账户信息表进行更新的时候,需要首先读取version字段。
操作员 A 此时将其读出( version=1 ),并从其帐户余额中扣除 $50( $100-$50 )。
在操作员 A 操作的过程中,操作员B 也读入此用户信息( version=1 ),并从其帐户余额中扣除 $20 ( $100-$20 )。 操作员 A 完成了修改工作,提交更新之前会先看数据库的版本和自己读取到的版本是否一致,一致的话,就会将数据版本号加1( version=2 ),连同帐户扣除后余额( balance=$50 ),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。 操作员 B 完成了操作,提交更新之前会先看数据库的版本和自己读取到的版本是否一致,但此时比对数据库记录版本时发现,操作员 B 提交的数据版本号为 2 ,而自己读取到的版本号为1 ,不满足 “ 当前最后更新的version与操作员第一次读取的版本号相等 “ 的乐观锁策略,因此,操作员 B 的提交被驳回。 这样,就避免了操作员 B 用基于 version=1 的旧数据修改的结果覆盖操作员A 的操作结果的可能。
2. CAS算法
这里只介绍简单的就说明,具体的底层执行机制请看我的另一个篇博客:
即compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三个操作数需要读写的内存值 V
进行比较的值 A 拟写入的新值 B 当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。
自旋锁和互斥锁的区别
自旋锁:乐观锁的一种,cas就是自旋锁, Atomic 的原子类的底层就是CAS实现的;本质就是无锁机制,效率高,
互斥锁:悲观锁的一种,当前线程获得锁进行执行时,其他线程会阻塞,等待当前线程执行完才可执行;效率低
发表评论
最新留言
关于作者
