加锁目的:由于线程执行的过程是不可控的,所以需要采用同步机制来协同对对象可变状态的访问。

  加锁方式:java锁分为两种--显示锁和隐示锁,本质区别在于显示锁需要的是程序员自己手动的进行加锁与解锁如ReentrantLock需要进行lock与unlock。而隐式锁则是Synchronized,jvm内置锁,jvm进行操作加锁与解锁。

Synchronized关键字

  每个对象创建后都会存在一个Monitor(监视器锁),它的实现依赖底层的系统的Mutex Lock(互斥锁)实现,是重量级锁,但是在java1.6版本之后,jvm内置锁进行了一系列的优化,如:锁粗化、锁消除、偏向锁、轻量级锁、重量级锁等。

  Synchronized锁编译成字节码后,会发现jvm底层使用了monitorenter与monitorexit来进行加锁与解锁

  

我们知道synchronized加锁加在对象上,对象是如何记录锁状态的呢?

  答案是锁状态是被记录在每个对象的对象头(Mark Word)中,下面我们一起认识一下对象的内存布局

对象的内存布局

  HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

  • 对象头:比如 hash码,对象所属的年代,对象锁,锁状态标志,偏向锁(线程)ID,偏向时间,数组长度(数组对象)等
  • 实例数据:即创建对象时,对象中成员变量,方法等
  • 对齐填充:对象的大小必须是8字节的整数倍

AQS具备特性

  • 阻塞等待队列
  • 公平/非公平
  • 可重入
  • 共享/独占
  • 允许中断

  例如Java.concurrent.util当中同步器的实现如Lock,Latch,Barrier等,都是基于AQS框架实现,一般通过定义内部类Sync继承AQS,将同步器所有调用都映射到Sync对应的方法

 static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L; /**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
} protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}

  该NonfairSync为ReentranLock内部类,默认非公平锁,非公平锁与公平锁的区别在于,当正在争抢的锁释放时,谁会抢到锁。我们再看一下 公平锁的lock()方法,就明白了。

 static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L; final void lock() {
acquire(1);
} /**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
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;
}
}

  前面看到非公平锁首先去尝试设置state状态是否可以成功,这里的state为Node节点的属性,默认为0就是没有人抢到锁的情况,加一次锁就会state进行CAS操作+1,因为它是可重入的锁没所以每加一次都会state+1,没释放一次都会-1,直到state为0时,才会轮到下一个线程进行抢锁。我们再看一下acquire(1)方法,就明白了。

 public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
我来讲接一下:tryAcquire(arg)是尝试获取锁,并将state状态由0变为+1,如果失败说明锁已经被别人抢走了,需要下一步操作就是addWaiter
       addWaiter()方法就是创建一个双向链表的结构的队列,看到它是一种Node.EXCLUSIVE模式创建的,为独占模式
       acquireQueued()方法让第一个节点去争抢锁,如果失败则会将该线程直接中断阻塞,如果当前节点的前一个节点为头结点也就是第一个节点抢到锁了将会把当前节点作为新的头结点并返回值
tryAcquire(arg)公平锁与非公平锁实现不一样,公平锁多判断了一步就是如果我的同步队列也就是等待队列里有其他等待的节点,将不会让当前的新线程去获取锁。

  释放锁的时候是一样的逻辑
 public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
  也是会改变state值的状态,当变为0时,Node还有一个属性就是exclusiveOwnerThread,它指向的是正在使用锁的线程,state释放为初始状态的时候,exclusiveOwnerThread将会置位null;去唤醒头结点。取消阻塞公平与非公平就在于去抢锁的时候判断是不一样的。

BlockingQueue实现原理

  我们以ArrayBlockingQueue为例讲解为什么AQS需要使用同步队列与条件队列两个队列;
  public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}

    源码中我们创建阻塞队列需要创建容量初始大小以及默认非公平锁,底层看到使用的是独占锁ReentrantLock以及两个条件队列Condition;

 public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}

  当我们往队列中添加元数时,则会将元素放入到数组中,并且唤醒阻塞线程;如果数组填满的时候,则会将当前阻塞,我们看看await方法;

 public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}

    我来讲解下上面的方法:

addConditionWaiter:将当前条件队列从后遍历进行删除已经没有用的节点,并且将当前线程添加到条件队列当中;返回当前节点。
fullyRelease:将当前线程的锁全部释放掉,并且当前的独占线程置位null后,唤醒队列的头节点,但是目前我们的队列还没有任何阻塞节点,所以只是释放了锁。
isOnSyncQueue:查看是否当前节点已经在同步队列中。
checkInterruptWhileWaiting:查看点前节点是否被中断,如果没有则将当前节点添加到同步队列当中。
acquireQueued:同步队列中头节点重新获取锁并返回。
unlinkCancelledWaiters:删除条件队列中被中断的节点。
reportInterruptAfterWait:中断当前线程。

  我们不难发现,当前节点没有添加成功会先添加到条件队列中,然后释放持有的独占锁,并且判断是否已经加入到了同步队列中去,没有的话线程阻塞到这里。一旦被释放就会

立即添加到同步队列中。然后做一些后续处理。

 public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}

  这里的await跟上面的步骤是一样的,只不过他会释放上一个阻塞的线程,让它添加到同步队列中。

ps:关注一下本人公众号,每周都有新更新哦!


线
访

Synchronized&Lock&AQS详解的更多相关文章

  1. (转)Java并发包基石-AQS详解

    背景:之前在研究多线程的时候,模模糊糊知道AQS这个东西,但是对于其内部是如何实现,以及具体应用不是很理解,还自认为多线程已经学习的很到位了,贻笑大方. Java并发包基石-AQS详解Java并发包( ...

  2. AQS详解

    一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronized(AQS)! 类如其名,抽象的队列式的同步器,AQ ...

  3. Java并发之AQS详解

    一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer(AQS)! 类如其名,抽象的队列式的同步器,AQ ...

  4. 【1】AQS详解

    概述: 它内部实现主要是状态变量state和一个FIFO队列来完成,同步队列的头结点是当前获取到同步状态的结点,获取同步状态state失败的线程,会被构造成一个结点加入到同步队列尾部(采用自旋CAS来 ...

  5. Java并发之AQS详解(转)

    一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronized(AQS)! 类如其名,抽象的队列式的同步器,AQ ...

  6. Java AQS详解(转)

    原文地址 一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer(AQS)! 类如其名,抽象的队列式的同 ...

  7. java中synchronized的用法详解

    记下来,很重要. Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchron ...

  8. Java 中 synchronized的用法详解(四种用法)

    Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码.本文给大家介绍java中 synchronized的用法,对本文感兴趣的朋友一起看看吧 ...

  9. Java 中 synchronized的用法详解

    Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 1.方法声明时使用,放在范围操作符(public等)之后,返回类型声明(void等)之 ...

随机推荐

  1. 7月清北学堂培训 Day 6

    今天是钟皓曦老师的讲授~ 合并石子拓展: 合并任意两堆石子,每次合并的代价是这两堆石子的重量的异或值,求合并成一堆的最小异或和. 状态设置:f [ s ] 把 s 所对应的石子合并的最小代价: 那么答 ...

  2. 1633:【例 3】Sumdiv

    #include<bits/stdc++.h> #define ll long long using namespace std; ; ll a,b,ans=,cnt; ll p[],c[ ...

  3. 初次Java web项目的建立以及与数据库的连接

    题目要求: 1登录账号:要求由6到12位字母.数字.下划线组成,只有字母可以开头:(1分) 2登录密码:要求显示“• ”或“*”表示输入位数,密码要求八位以上字母.数字组成.(1分) 3性别:要求用单 ...

  4. JavaWeb_(SpringMVC框架)SpringMVC&Spring&MyBatis整合

    JavaWeb_(SpringMVC框架)测试SpringMVC&Spring&MyBatis三大整合 传送门 1.整合ssm 3大框架 过程 a)导包 -> spring_Ja ...

  5. Spring框架AOP

    aop技术是面向切面编程思想,作为OOP的延续思想添加到企业开发中,用于弥补OOP开发过程中的缺陷而提出的编程思想.AOP底层也是面向对象:只不过面向的不是普通的Object对象,而是特殊的AOP对象 ...

  6. JAVA基础知识|异常

    一.基础知识 处理异常,java提供了一个优秀的解决方案:异常处理机制. java把异常当作对象来处理,所有的异常都是由Throwable继承而来,但在下一层立即分解为两个分支:Error和Excep ...

  7. 配置连接远程ip的Redis

    1.修改redis服务器的配置文件redis.windows.conf 注释绑定的主机地址 # bind 127.0.0.1 修改redis的守护进程为no,不启用 daemonize "n ...

  8. Promise初尝试

    promise.ts export function showAlert() { console.log("开始调用showAlert"); return new Promise( ...

  9. I am a legend: Hacking Hearthstone with machine-learning Defcon talk wrap-up

    I am a legend: Hacking Hearthstone with machine-learning Defcon talk wrap-up: video and slides avail ...

  10. 用ConfigMap管理配置(10)

    一.ConfigMap介绍管理配置:   ConfigMap介绍 Secret 可以为 Pod 提供密码.Token.私钥等敏感数据:对于一些非敏感数据,比如应用的配置信息,则可以用 ConfigMa ...