Synchronized
1.基本概念
- 是非公平可重入锁
- 字节码层面 是monitor enter 获取锁,monitor exist 释放锁
普通同步方法:锁是当前实例对象;
静态同步方法:锁是当前类的class对象;
同步方法块:synchronized括号里的对象。
2.锁的几种状态
- 偏向锁
1. 只有一个线程使用时,是偏向锁 ,将当前线程id记录在对象的markword中
2. 场景:jvm启动时 会多线程竞争锁,如果启动偏向锁需要大量锁撤销,所以直接是到轻量级锁,默认4秒后启动偏向锁。
3. xx:BiasedLockingStartupDelay=0 (不延时启动偏向锁的参数)
- 轻量级锁(自旋)
1. 对象的markword中会有指针记录是哪个线程争抢到了这个锁,这个指针指向线程栈中的lock record。
2. 两(多)个线程竞争, 线程A,B 有各自的线程栈, 在栈里生成各自的 lock record ,如果A先抢到锁, B则继续自旋,直到A释放锁,B在继续争抢锁。
3. 自旋是占用cpu资源的,如果锁时间过长,或者自旋的线程数过多,Cpu资源会被大量占用和消耗
- 重量级锁
1. 会记录在objectMonitor一个字段上
2. 重量级锁有等待队列 waitSet,拿不到锁的线程进入等待队列,不需要消耗cpu资源
- 几种状态的相互转化
1. new 对象(普通)
开启偏向锁-->偏向锁-->轻度竞争,轻量级锁
未开启偏向锁-->轻量级锁-->有现成超过10次自旋,或者自旋线程数超过cpu核数的一半--->重量级锁
偏向锁-->重量级锁 :重度竞争或者调用wait()
- 区别
1.偏向锁,轻量级锁 :用户空间完成的
2.重量级锁 : 需要向内核申请
3.用户态和内核态:操作系统 ring0-ring3级 。 jvm是用户态,用synchronized加锁需要向os操作系统申请加锁,即用户态调用内核态 0X80
3.锁的重入
- 偏向锁&自旋锁:
- 线程栈里的lock record 里有一个指针会记录 前一个锁状态的 备份markword ;
- 重入的时候 线程栈里会记录多个指针为空的lock record 然后解锁的时候会一个一个弹出
- reentrantlock里可重入的是state++
ReentrantLock
-
公平/非公平 可重入锁(自旋锁)
-
与synchronized相比 更灵活。
1.tryLock,lockInterruptibly- 如果这个线程争抢锁的时候是阻塞状态waiting,其他线程调用该线程的打断方法可以将其打断
-
公平、非公平
1. 公平锁:new ReentrantLock(true) 在并发环境中,每个线程在获取锁的时候,会先查看锁维护的等待队列,如果为空或者当前线程是等待队列里的第一个,就占有锁,否则就会加入到等待队列中,先进先出的原则,等待队列为双向链表 2. 非公平:new ReentrantLock(),直接越过等待队列尝试占有锁,否则按照公平锁的方式
-
源码:
- 非公平: lock的时候,直接cas判断state值,如果能把0更新为1就将锁设置为独占的。 否则执行公平锁的逻辑。 - 公平: acquire(1); 尝试是否能更新state值并设置独占,如果是同一个线程重入锁,则将state值+1.否则将当前线程加到等待 队列中。 - ReentrantLock implements Lock 并且将Sync 作为他的属性 - Sync 继承 AQS . - ReentrantLock.lock()也是调用的Sync.lock() - Sync为抽象类,子类为FairSync 和NonfairSync - acquire 获取独占的节点,没有获取到就排队,lock的时候会调用 - release 释放独占的节点,由一个或者多个非阻塞线程实现该操作,用于unlock - unsafe.cas - volatile int state - addWaiter cas - tryAcquire,tryRelease 由子类reentrantLock实现
-
可重入
-
ReentantLock和Synchronized都是可重入锁
-
作用:防止死锁
-
sync m1() 调用 sync m2() 他们持有的是同一把锁
-
例子:
lock.lock() lock.lock() lock.unlock() lock.unlock() 两个lock和unlock也会执行成功 如果两个lock 一个unlock会阻塞住,等待解锁,不会编译和执行报错 如果多加unlock 会抛IllegalMonitorStateException异常
-
-
Condition
- lock.newCondition(); - condition.await(); - condition.signal(); - 需要与lock一起使用,生产者消费者模型
-
AQS
ReadWriteLock
实现类:ReentrantReadWriteLock
读写分离 rwl.writeLock().lock(); rwl.readLock().lock();
写:独占锁,原子性
读:共享锁,同时进行
写写、读写 不能共存
ps:lock.lock()写在try外面,因为lock时候异常,finally 会执行unlock也会异常
说明:多个线程(包含读写争抢同一把锁),多个读线程可以同时争抢到然后同时执行,多个写线程按顺序执行,多个读线程(同时执行)与写线程按顺序执行
StampedLock
与reentrantlock相同的功能
new StampedLock()
lock.readLock(); //unlockRead
lock.writeLock(); //unlockWrite
乐观读
读多写少的场景适用,防止一直是读线程争抢到锁,导致写线程不执行。
long stamp = lock.tryOptimisticRead();
if(lock.validate(stamp)){
lock.readLock();
}
synchronized,readWritelock, stampedLock,Optimistic,对比
synchronized一直表现很平均,甚至是有时候比其他的效率高。
需要根据具体场景进行压测在选择
Condition
LockSupport
park() 如果可以获取许可,直接返回,否则阻塞住counter=0。
unpark() 释放一个许可 counter = 1。 counter值最多为1.
Semapore(1)
AQS,CAS,unsafe类
这些锁的比较,本质上的总结。
练习
1.实现生产消费模式(synchronized,队列,condition...),实现一个锁(读写锁,自旋锁,信号量锁)
2.手写一个读写锁,分别用synchronized 和cas方式