系列文章目录
线程安全(一)java对象头分析以及锁状态
线程安全(二)java中的CAS机制
线程安全(三)实现方法sychronized与ReentrantLock(阻塞同步)
线程安全(四)Java内存模型与volatile关键字
线程安全(五)线程状态和线程创建
线程安全(六)线程池
线程安全(七)ThreadLocal和java的四种引用
线程安全(八)Semaphore
线程安全(九)CyclicBarrier
线程安全(十)AQS(AbstractQueuedSynchronizer)
0.前言
synchronized与lock的区别,以及ReentrantLock和ReentrantReadWriteLock的实现
1.sychronized
1.1.简介
根据《深入理解Java虚拟机(第二版)》上讲的,线程安全最基本的互斥同步手段就是synchronized关键字。
- synchronized进过编译会在同步块前和后形成monitorenter,monitorexitr两个字节码指令,这两个字节码都需要reference类型的参数来指明锁定和解锁的对象。
- 如果明确指出要锁定和锁所对象,那就是这个对象的reference,没有明确指定,就根据修饰的是实例方法还是类方法去取对应的对象实例或class对象来作为锁对象。
- 在执行monitorenter指令时,首先要尝试获取对象的锁。如果没锁定,或当前线程已拥有对象的锁,就把锁的计数器加1,在执行monitorexit指令时,会减一,当计数器为0时,锁就被释放。如果获取失败,那当前线程就阻塞等待,直到对象锁被另一个线程释放。
- synchronized同步块对同一条线程是可重入的,不会出现自己锁死的问题。
- 用户态到核心态,状态切换可能需要更长的时间。
1.2.底层原理
当对象的锁级别升级为“重量级锁”时,JVM就开始采用Object Monitor机制控制各线程抢占对象的过程了。
内置锁(ObjectMonitor)
通常所说的对象的内置锁,是对象头Mark Word中的重量级锁指针指向的monitor对象。
//结构体如下
ObjectMonitor::ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
_recursions = 0; //线程的重入次数
_object = NULL;
_owner = NULL; //标识拥有该monitor的线程
_WaitSet = NULL; //等待线程组成的双向循环链表,_WaitSet是第一个节点
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ; //多线程竞争锁进入时的单向链表
FreeNext = NULL ;
_EntryList = NULL ; //_owner从该双向循环链表中唤醒线程结点,_EntryList是第一个节点
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
线程大体会处于三个状态
- Entry Set:没有操作权限,会待进入监控区部分(Entry Set),停留在这个区域的线程由于还没有获得对象操作权限的原因,停留在synchronized同步块以外,其线程状态被标识为BLOCKED。
- Owner:持有对象操作权(only one),当前持有对象操作权限的线程互斥量将被记录在这个对象的对象头中。
- Wait Set(待授权区):某个线程通过wait等相关方法释放了对象的操作权限,会被放置到待授权区域(Wait Set只有通过notify()或者相似方法被通知转移的线程能够参与线程抢占。
1.3.synchronized锁升级
无锁 - 偏向锁 -轻量级锁(自旋锁)-重量级锁
具体见java对象头分析与锁升级
偏向锁 - markword 上记录当前线程指针,下次同一个线程加锁的时候,不需要争用,只需要判断线程指针是否同一个,所以,偏向锁,偏向加锁的第一个线程 。hashCode备份在线程栈上 线程销毁,锁降级为无锁
有竞争 - 锁升级为轻量级锁 - 每个线程有自己的LockRecord在自己的线程栈上,用CAS去争用markword的LR的指针,指针指向哪个线程的LR,哪个线程就拥有锁
自旋超过10次,升级为重量级锁 - 如果太多线程自旋 CPU消耗过大,不如升级为重量级锁,进入等待队列(不消耗CPU)-XX:PreBlockSpin
1.4.sychronized同步方法与非同步方法
1.5.总结
1.JVM字节码
monitorenter指令
...
monitorexit指令
2.两种锁
static同步方法是类锁,非static是对象锁
3.可重入原理(加锁次数计数器)
4.不可中断
5.方法抛出异常后,会释放锁
6.notify, 和notifyAll 都没有释放对象的锁,而是在Synchronizer同步块结束的时候释放
2.Lock
2.1.Lock方法介绍
2.2.Lock与synchronized的比较
优点
(1)可以尝试非阻塞地获取锁。
(2)获取锁过程中可以被中断。
(3)超时获取锁,可以避免线程长时间阻塞。
需要注意要主动释放锁
模板如下:
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
原理:多个线程会放到node队列
3.ReentrantLock
3.1 简介
也是可重入锁,多了以下特点
1.等待可中断
2.可实现公平锁
3.锁可以绑定条件
3.1.非公平锁(默认)
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 5; i++) {
new Thread(() -> getReentrantLock(), "线程"+ i).start();
}
}
static void getReentrantLock() {
try{
System.out.println(Thread.currentThread().getName() + " 准备 获取锁");
lock.lock();
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 获取锁");
} catch (Exception e){
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName() + " 准备 释放锁");
lock.unlock();
}
}
3.2.公平锁
static ReentrantLock lock = new ReentrantLock(true);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 5; i++) {
new Thread(() -> getReentrantLock(), "线程"+ i).start();
}
}
static void getReentrantLock() {
try{
System.out.println(Thread.currentThread().getName() + " 准备 获取锁");
lock.lock();
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 获取锁");
} catch (Exception e){
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName() + " 准备 释放锁");
lock.unlock();
}
}
3.3.非公平锁与公平锁比较
构造方法多了一个参数static ReentrantLock lock = new ReentrantLock(true);
源码比较
class FairSync
final void lock() {
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 查询是否有线程在排队等待获取锁,如果有线程在排队,则不去抢锁
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
class NonfairSync
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());//只是赋值当前线程
else
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 不查询,直接抢锁
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
3.4 Condition
模拟一个场景,当银行卡余额小于1000时,发送短信通知
public class RichMan {
private long monuey;
private Lock lock = new ReentrantLock();
private Condition balanceConditon = lock.newCondition();
public RichMan(long monuey) {
this.monuey = monuey;
}
public void huafei(long price) {
lock.lock();
try {
this.monuey = this.monuey - price;
balanceConditon.signal();// 每次消费完唤醒等待线程
System.out.println(Thread.currentThread().getName() + "-》花费:" + price + ", 余额:" + monuey);
} finally {
lock.unlock();
}
}
public void waitBuy() {
lock.lock();
try {
while (this.monuey > 1000L) {
balanceConditon.await();
System.out.println(Thread.currentThread().getName() + " is be notify");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
sendMsg();
}
public void sendMsg() {
System.out.println(Thread.currentThread().getName() + "-》您的余额为不足1000元,请充值");
}
}
private static RichMan richMan = new RichMan(1010L);
private static class CheckMoney extends Thread {
@Override
public void run() {
richMan.waitBuy();
}
}
public static void main(String[] args) throws InterruptedException {
Thread t = new CheckMoney();
t.setName("checkMoney");
t.start();
Thread.sleep(2000);
for (int i = 0; i < 40; i++) {
richMan.huafei(3L);
Thread.sleep(500L);
}
}
3.4.1Condition类和Object类锁方法区别
- Condition类的await方法 和 Object类的wait方法等效。
- Condition类的signal方法 和 Object类的notify方法等效。
- Condition类的singnalAll方法 和 Object类的notifyAll方法等效。
- ReetrantLock类可以唤醒指定条件的此线程,二object的唤醒是随机的。
3.5.ReentrantReadWriteLock
private UseRwLock useRwLock;
public static void main(String[] args) throws InterruptedException {
UseRwLock useRwLock = new UseRwLock(new User(1));
for (int i = 0; i < 5; i++) {
new Thread(new ReadThread(useRwLock)).start();
}
for (int i = 0; i < 1; i++) {
new Thread(new WriteThread(useRwLock)).start();
}
}
private static class ReadThread extends Thread {
private UseRwLock useRwLock;
public ReadThread(UseRwLock useRwLock) {
this.useRwLock = useRwLock;
}
@Override
public void run() {
useRwLock.getNumber();
System.out.println("get");
}
}
private static class WriteThread extends Thread {
private UseRwLock useRwLock;
public WriteThread(UseRwLock useRwLock) {
this.useRwLock = useRwLock;
}
public WriteThread(int i) {
this.useRwLock = useRwLock;
}
@Override
public void run() {
useRwLock.setNumber(1);
System.out.println("set");
}
}
public static class UseRwLock {
private User user;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock getLock = lock.readLock();//读锁
private final Lock setLock = lock.writeLock();//写锁
public UseRwLock(User user) {
this.user = user;
}
public int getNumber() {
getLock.lock();
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
getLock.unlock();
}
return this.user.getNumber();
}
public void setNumber(int number) {
setLock.lock();
try {
Thread.sleep(5);
user.setNumber(number);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
setLock.unlock();
}
}
}
public static class User {
private int number;
public User(int number) {
this.number = number;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
总结
1.线程进入读锁的前提
没有其他线程的写锁
没有写请求或者有写请求,但调用线程与持有线程是同一个
2.线程进入写锁的前提条件:
没有其他线程的读锁
没有其他线程的写锁
可重入,写锁可降级为读锁