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

  加锁方式: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. 使用Ajax向SpringMVC传递Json数据

    这篇文章已经过时了. 请参考比较合适的前后端交互方式. 1.保证SpringMVC配置成功了. 2.在pom.xml中追加Jackson相关的依赖 <dependency> <gro ...

  2. Java 生成六位短信验证码

    在<Java 生成三位随机数>中,简要介绍了使用Java生成三位随机数的方法,前几天在工作中遇到生成6位短信验证码的需求,验证码由6位随机数字构成,不包含字母.6位随机数通常用作短信验证码 ...

  3. JAVA RPC (九) netty服务端解析

    源码地址:https://gitee.com/a1234567891/koalas-rpc 企业生产级百亿日PV高可用可拓展的RPC框架.理论上并发数量接近服务器带宽,客户端采用thrift协议,服务 ...

  4. 为ubuntu安装powerline记录

    C:\Users\win10的用户名\AppData\Local\Packages\CanonicalGroupLimited.Ubuntu18.04onWindows_79rhkp1fndgsc\L ...

  5. spring cloud学习

    https://gitee.com/ylimhhmily/SpringCloudTutorial/tree/master hystrix配置不生效问题https://blog.csdn.net/lvy ...

  6. 北大ACM - POJ试题分类

    1.入门水题 可用于练手与增强自信 POJ-1003POJ-1004 POJ-1005 POJ-1207 POJ-3299 POJ-2159 POJ-1083POJ-3094 2.初级 2.1. 基本 ...

  7. 如何在国内使用google

    而Google却一直坚持“机器算法”至上,让信息以公正的排序结果呈现,对于IT人员来说国内不能用google进行搜索是很痛苦的. 公司邮件介绍了一些方法,mark一下还是很有用的. http://ww ...

  8. linux挂载iscsi出现的问题

    挂载的时候我们可以使用永久挂载但是这里我们就需要注意了正常的永久挂载可能会导致系统起不来因为这里我们使用的是服务端的硬盘为网络设备而开机的读取中 会先读取/etc/fstab下的内容 之后再读取网络的 ...

  9. PAT 甲级 1027 Colors in Mars (20 分)(简单,进制转换)

    1027 Colors in Mars (20 分)   People in Mars represent the colors in their computers in a similar way ...

  10. HOSTS大法解决Github Clone太慢

    经常要clone github中的一些项目,无奈如果不爬梯子的话速度实在是龟速,经常1k/s,于是搜了下解决方法,改HOSTS大法. Windows下在C:/Windows/system32/driv ...