锁的分类
乐观锁
针对同步处理的方案
AQS是悲观锁;CAS是乐观锁
悲观锁就是利用各种锁机制;乐观锁就是无锁编程,常见的样例就是CAS
偏向锁、轻量级锁和重量级锁
是针对synchronized锁的三种状态。这三种锁状态是通过对象在对象头中的特定字段来表示的,实际上
就是使用Object Minitor控制多线程的工作过程并进行协调处理
偏向锁 只有一个线程进入临界区使用偏向锁
轻量锁 多个线程交替进入临界区使用轻量级锁
重量级锁 多个线程同时进入临界区使用重量级锁
自旋锁
执行几个空方法,稍微等一会再无尝试加锁
自适应自旋锁
线程等待锁释放时执行的自旋次数不是固定值
synchronized和Lock
synchronized和Lock都是可重入锁,但是处于竞争阻塞状态时synchronized不可中断,但是lock可以中断。
synchronized源码实现使用了对象头中Mark word标识对象加锁状态,当处于重量级锁时编译成为2个指令码monitorenter和mointorexit指令获取和释放锁;synchronized语义底层是monitor对象来完成的;sychronized方法的同步并没有依赖指令码实现,使用常量ACC_SYNCHRONIZED标识符。Lock的底层实现是通过CAS乐观锁和AQS类来实现的,它把所有的请求线程构建成一个自旋锁队列,当队列中的操作通过CAS实现。
synchronized是关键字,内置语言实现,Lock是接口,常见实现类为ReentrantLock。
synchronized在线程发生异常时自动释放锁,所以不会产生死锁,但是Lock异常不会自动释放锁,需要在finally中实现锁的释放。
Lock有读写锁,可以提供多线程并发度的效率。
synchronized和Lock
synchronized | Lock | |
优势 | 实现简单,语义清晰,便于JVM堆栈跟踪,加锁和解锁过程由JVM自动控制,同时提供了多种优化 方案,使用更为广泛 | lock是一个可定时的、可轮询的和可中断的锁操作,提供了重入锁、重入读写锁、公平锁和非公 平锁 |
缺点 | 属于悲观的排他锁,不能进行高级功能 | 需要手动释放锁,不适合JVM进行堆栈跟踪 |
锁类型 | 对象锁,非静态方法 类锁,静态方法 私有锁,同步代码块 | 可重入锁ReentrantLock 读写锁ReentrantReadWriteLock,注意ReentrantReadWriteLock不是具体的锁实现,其中包含了 readLock读锁和writeLock写锁 |
锁特性 | 锁具有可重入性,不需要多次释放 当前线程获取到一个对象的锁之后,允许继续申请其它对象的锁 锁属于排他锁 每个类只有一个类锁,但是类可以实例化为对象,每个对象对应一个锁 类锁和对象锁不会产生竞争,私有锁和对象锁也不会产生竞争 一般建议使用私有锁,以减少锁的颗粒度,减少由锁产生的开销 | 需要显式的进行锁的释放,特别是出现异常时,synchronized会自动释放锁,而ReentrantLock不 会自动释放锁,所以必须使用try/finally结构保证释放操作unlock 公平锁,默认使用非公平锁,可以构建对象时使用true参数标识使用公平锁 可重入,重入多次释放多次 可中断 超时机制,超时后不能获取锁不会造成死锁 ReentrantLock时很多类的实现基础,例如JDK1.7的ConcurrentHashMap的分段锁Segment就是 继承ReentrantLock,在CopyOnWriteArray中也使用了ReentrantLock |
同步集合和并发集合
同步集合类:Hashtable、Vector 方法上一般都有同步约束,古老实现
同步集合包装类:Collections.synchronizedMap(new HashMap())或者
Collections.synchronizedList(new ArrayList()) 实现原理是使用全局锁
并发集合:ConcurrrentHashMap、CopyOnWriteArrayList、CopyOnWriteHashSet
性能比较
同步集合比并发集合会慢很多,主要原因是锁,同步集合会对整个集合加锁
并发集合的实现原理
ConcurrentHashMap在JDK1.7使用分段锁,只对相关的片段上加锁,同时允许多线程访问其它未上锁的片段;在JDK1.8中不使用分段锁,而是选用针对桶中的链表或者红黑树的第一个节点进行CAS实现无锁的线程安全操作。
CopyOnWriteArrayList允许多个线程以非同步的方式进行读取操作,当有线程写操作时它会将整个List复制一个副本进行修改,修改完成后再使用副本替换原始数据。如果在读多写少的情况下使用并发集合比使用同步集合具有更好的并发性。
ConcurrentHashMap实现原理
JDK1.7的实现:分段锁+【数组+链表】
好处:针对部分元素进行加锁,其余段不锁定,可以提高并发能力
hash过程比普通hashmap次数多
JDK1.8的实现:CAS乐观锁+【数组+链表+红黑树】
不使用锁,并发性提高
并发处理模型
CAS模型
CAS就是比较并交换,用于解决多线程并发情况下使用锁造成性能耗损的一种机制,采用无锁的方式实现线程同步的效果。CAS操作包括了3个操作数:内存位置、预期原始数据和新计算的数据。如果内存位置的值与预期值相匹配,则自动将该位置上的数据更新为新值,否则处理器不作任何处理。同时都会在CAS执行之前返回该位置上的值。比较和交换是用于实现多线程同步的原子指令,是作为单个原子操作完成的,原子性保证新值基于最新信息进行计算。
特性:
通过调用JNI的代码实现
非阻塞算法
非独占锁
存在问题:
ABA问题
循环时间开销大
只能保证一个共享变量的原子操作
原子类
java1.5中引入了AutomicInteger、AutomicLong、AutomicReference等原子类,提供了
compareAndSet、incrementAndSet和getAndIncrement等方法都使用了CAS操作,都是由硬件保证CAS操作的原子性。
原子的意思是操作不可再分,因此是线程安全的。
使用原子类实现的计数器,当然也可以通过volatile和synchronized实现,但是过于繁琐。
AtomicInteger具体实现
Unsafe类是在sun.misc包下,不属于Java标准。但是很多Java的基础类库,包括一些被广泛使用的高性能开发库都是基于Unsafe类开发的,比如Netty、Cassandra、Hadoop、Kafka等。Unsafe类在提升Java运行效率,增强Java语言底层操作能力方面起了很大的作用。
Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,同时也带来了指针的问题。过度的使用Unsafe类会使得出错的几率变大,因此Java官方并不建议使用的,官方文档也几乎没有。Oracle正在计划从Java中去掉Unsafe类,如果真是如此影响就太大了。
多线程同步。包括锁机制、CAS操作等。这部分包括了compareAndSwapInt、compareAndSwap等方法。Unsafe类的CAS操作可能是用的最多的,它为Java的锁机制提供了一种新的解决办法,比如AtomicInteger等类都是通过该方法来实现的。compareAndSwap方法是原子的,可以避免繁重的锁机制,提高代码效率。这是一种乐观锁,通常认为在大部分情况下不出现竞态条件,如果操作失败,会不断重试直到成功。
CAS模型的问题
ABA问题
Java中提供了AtomicStampedReference和AtomicMarkableReference来判断处理ABA问题的,
主要是在对象中额外添加一个标记用于标识对象是否有过变更
CAS应用场景默认资源的竞争是比较短暂的,否则不断的自旋尝试会有过度消耗CPU的问题
解决方案是引入超时设置
CAS只能保证一个共享变量的原子操作,解决方法是使用锁或者合并多个变量为一个对象