JAVA中主要锁

synchronized

Reentrantlock

ReentrantReadWriteLock

问题引入

为什么需要锁?

为什么JAVA有了synchronize还需要Reentrantlock和ReentrantReadWriteLock?

synchronize和lock分别怎么实现同步快(原子性,一致性,禁重排序)?

synchronize和lock分别怎么实现锁的优化,可重入锁,偏向锁?

lock如何实现公平锁(synchronize是非公平锁)?

为什么需要锁?

目的:

锁的目的是防止资源的竞争,主要从  原子性(一致性),可见性,防止处理重排序 三个方面来处理, volatile满足了后面两个特性,JAVA从两方面来实现锁

为什么JAVA有了synchronize还需要Reentrantlock和ReentrantReadWriteLock?

synchronized与ReentrantLock ,使用上看区别

1, synchronize在获取锁阻塞的时候是不能打断的

2, synchronize无超时机制,阻塞了的话只能一直阻塞造成死锁

3,synchronize只能notify,wait,如果需要两个或以上条件就不能用了,如: JAVA阻塞队列的实现,需要用是否为空和是否已满两个条件来阻塞线程

看lock相关的API就知道, 主要就是解决这几个问题

方法名称 描述
lock 获取锁,如果锁无法获取,那么当前的线程就变为不可被调度,直到锁被获取到
lockInterruptibly 获取锁,除非当前线程被中断。如果获取到了锁,那么立即返回,如果获取不到,那么当前线程变得不可被调度,一直休眠直到下面两件事情发生:1、当前线程获取到了锁

2、其他的线程中断了当前的线程

tryLock 如果调用的时候能够获取锁,那么就获取锁并且返回true,如果当前的锁无法获取到,那么这个方法会立刻返回false
tryLcok(long time,TimeUnit unit) 在指定时间内尝试获取锁如果可以获取锁,那么获取锁并且返回true,如果当前的锁无法获取,那么当前的线程变得不可被调度,直到下面三件事之一发生:1、当前线程获取到了锁

2、当前线程被其他线程中断

3、指定的等待时间到了

unlock 释放当前线程占用的锁
newCondition 返回一个与当前的锁关联的条件变量。在使用这个条件变量之前,当前线程必须占用锁。调用Condition的await方法,会在等待之前原子地释放锁,并在等待被唤醒后原子的获取锁
 

那ReentrantReadWriteLock呢?

读写锁用于读多写少的情况,即当一条线程获取写锁后,后面的读锁都被阻塞,等待获取写锁的线程完成释放。

场景,如本地缓存失效,当需要去DB拿数据进行写入的操作,需要阻塞其它读的操作.

当然,读写锁也是可以基于notifyAll和wait实现

需要注意的是

  1. 如果无写锁,读是不阻塞,
  2. 持有读锁后,不能直接调用写锁的lock方法 ,否则会造成死锁

synchronize和lock分别怎么实现同步快(原子性,一致性,禁重排序)?

synchronized锁

实现依赖

原子性,可见性和重排序都是依靠指令。方法同步和代码块同步依靠Monitor指令,代码块同步是使用monitorenter和monitorexit指令实现

锁信息保存在JAVA对象头里,准确说是Mark Word

synchronize的阻塞,依靠几个队列,属于不公平锁(线程先CAS竞争锁,再进队列)

ContentionList(LIFO)-->EntryList(LIFO)-->OnDeck-->Owner-->Wait Set  (http://www.cnblogs.com/lykm02/p/4516777.html )

锁的转换方面

无锁-->偏向锁-->轻量锁-->重量锁  (http://blog.csdn.net/xad707348125/article/details/47189107)

synchronize和lock分别怎么实现锁的优化,可重入锁,偏向锁?

偏向锁和可重入锁的实现

可重入锁,即当本线程进入同一锁时可以进行多次上锁,当然也需要多次释放

偏向锁,即当获取线程再次进入同步块时不需要再次竞争(CAS),当某个Core CAS成功时必然会引起总线风暴,这就是所谓的本地延迟,本质上偏向锁就是为了消除CAS,降低Cache一致性流量

原理:

1, 锁保存当前锁的线程,判断同一个线程时允许

用一个计数器去记录当前重入的次数,当进入时计数器+1, 当释放锁时计算器-1, 当为0时表示可竞争

synchronized 

实现方式, 会在 Mark Word 存储获取锁线程的ID,然后栈帧中也存储线程ID,以后该线程再次进入同步块(同步方法)时不需要花费CAS了。

lock 

实现方式,  用JAVA代码实现处理,跟踪下  lock()(NonfairSyncCAS获取锁失败)->acquire(AQS尝试获取锁)-->tryAcquire(nonfairTryAcquire)-->nonfairTryAcquire(Sync 如下处理):

   final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) { //再次判断是否是未锁状态,state为0为未有线程获取锁
if (compareAndSetState(0, acquires)) { //CAS再次竞争获取锁,此处是公平锁与非公平锁的区别
setExclusiveOwnerThread(current);
return true;
}
}
//这里是是实现偏向锁的关键,比较如果是当前锁就不进入CLH队列后面的竞争了
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;
}

  

lock如何实现公平锁(synchronize是非公平锁)?

synchronize本身是非公平锁,无公平性实现.

lock非公平锁代码(详细如上)

if (compareAndSetState(0, acquires)) {  //如锁被释放,是非公平锁的话,用CAS再次竞争获取锁

公平锁此段代码如下:

  protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {//如锁被释放,必须当队列为空时才去CAS竞争锁
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;
}

  

lock如何实现公平锁(synchronize是非公平锁)?

synchronize本身是非公平锁,无公平性实现.

lock非公平锁代码(详细如上)

if (compareAndSetState(0, acquires)) {  //如锁被释放,是非公平锁的话,用CAS再次竞争获取锁

公平锁此段代码如下:

   protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {//如锁被释放,必须当队列为空时才去CAS竞争锁
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;
}

常见的锁实现原理总结

自旋锁

多个线程一起用CAS去尝试获取一个共同的可见性(volatile)的变量,获取成功即为获取锁

如 所有线程都运行

/**
* 自旋锁方式去实现阻塞
*
* 缺点:无法实现公平性,如果大量使用会增加CPU的Cache一致性流量开销
*/
public static void CASLock() {
// 不断去获取CAS的锁,如成功表示获取锁成功
while (state.compareAndSet(0, 1)) {
}
} public static void CASUnlock() { if (!state.compareAndSet(1, 0)) {
// 释放锁异常
throw new RuntimeException();
}
}

  

排队自旋锁 (Ticket Lock)

import java.util.concurrent.atomic.AtomicInteger;

public class TicketLock {
private AtomicInteger serviceNum = new AtomicInteger(); // 服务号
private AtomicInteger ticketNum = new AtomicInteger(); // 排队号 public int lock() {
// 首先原子性地获得一个排队号
int myTicketNum = ticketNum.getAndIncrement(); // 只要当前服务号不是自己的就不断轮询
while (serviceNum.get() != myTicketNum) {
} return myTicketNum;
} public void unlock(int myTicket) {
// 只有当前线程拥有者才能释放锁
int next = myTicket + 1;
serviceNum.compareAndSet(myTicket, next);
}
}

CLH锁

CLH是在前驱节点的属性上自旋,

组成一个队列后,每个节点都有保存当前节点获取锁的状态,和前一个节点的指向,获取锁的步骤

1, 新加个节点,并把节点通过自旋指向tail节点

2, 成功后,不停判断指向节点的锁状态,当前节点锁释放时获取锁

3, 释放锁,改变自身的锁持有状态就行

MCS锁

而MCS是在本地属性变量上自旋。

欢迎关注我的公众号, 一起来构建我们的知识体系

基础篇系列,JAVA的并发包 - 锁的更多相关文章

  1. 撸基础篇系列,JAVA的NIO部分

    前言:撸基础篇系列,避免每次都要从头开始看,写个自己的知识体系树 NIO 核心就是异步, 比如,复制文件,让操作系统去处理,等通知 BIO核心类 一,BIO NIO基本操作类 Bytebuffer 构 ...

  2. 【目录】mysql 基础篇系列

    随笔分类 - mysql 基础篇系列 mysql 开发基础系列22 SQL Model(带迁移事项) 摘要: 一.概述 与其它数据库不同,mysql 可以运行不同的sql model 下, sql m ...

  3. oracle(sql)基础篇系列(五)——PLSQL、游标、存储过程、触发器

      PL/SQL PL/SQL 简介 每一种数据库都有这样的一种语言,PL/SQL 是在Oracle里面的一种编程语言,在Oracle内部使用的编程语言.我们知道SQL语言是没有分支和循环的,而PL语 ...

  4. oracle(sql)基础篇系列(五)——PLSQL、游标、存储过程、触发器

    PL/SQL PL/SQL 简介 每一种数据库都有这样的一种语言,PL/SQL 是在Oracle里面的一种编程语言,在Oracle内部使用的编程语言.我们知道SQL语言是没有分支和循环的,而PL语言是 ...

  5. 基础篇:JAVA集合,面试专用

    没啥好说的,在座的各位都是靓仔 List 数组 Vector 向量 Stack 栈 Map 映射字典 Set 集合 Queue 队列 Deque 双向队列 关注公众号,一起交流,微信搜一搜: 潜行前行 ...

  6. 基础篇:JAVA原子组件和同步组件

    前言 在使用多线程并发编程的时,经常会遇到对共享变量修改操作.此时我们可以选择ConcurrentHashMap,ConcurrentLinkedQueue来进行安全地存储数据.但如果单单是涉及状态的 ...

  7. 基础篇:java GC 总结,建议收藏

    垃圾标记算法 垃圾回收算法 major gc.mini gc.full gc.mixed gc 又是什么,怎么触发的 垃圾回收器的介绍 Safe Point 和 Safe Region 什么是 TLA ...

  8. oracle(sql)基础篇系列(一)——基础select语句、常用sql函数、组函数、分组函数

        花点时间整理下sql基础,温故而知新.文章的demo来自oracle自带的dept,emp,salgrade三张表.解锁scott用户,使用scott用户登录就可以看到自带的表. #使用ora ...

  9. Spring基础篇——通过Java注解和XML配置装配bean

    自动化装配的确有很大的便利性,但是却并不能适用在所有的应用场景,比如需要装配的组件类不是由自己的应用程序维护,而是引用了第三方的类库,这个时候自动装配便无法实现,Spring对此也提供了相应的解决方案 ...

随机推荐

  1. doubango介绍

    1.doubango官网:http://www.doubango.org/ 2.doubango是一个开源的VOIP基础平台, 并能用于嵌入式和桌面系统的开源框架,该框架使用ANSCI-C编写,具有很 ...

  2. VS2013使用WebDeploy发布网站到IIS服务器

    VS2013用Web Deploy方式发布网站到IIS服务器发布文档 VS版本:VS2013 服务器版本:Windows Server 2012 R2 IIS版本:IIS8.0 Web Deploy版 ...

  3. Android项目实战(二十九):酒店预定日期选择

    先看需求效果图: 几个需求点: 1.显示当月以及下个月的日历 (可自行拓展更多月份) 2.首次点击选择"开始日期",再次点击选择"结束日期" (1).如果&qu ...

  4. 不常见但很有用的chrome调试工具使用方法

    前面的话   对于chrome调试工具,常用的是elements标签.console标签.sources标签和network标签.但实际上,还有一些不太常见但相当实用的方法可以提高网页调试效率.本文将 ...

  5. 支撑Pinterest日均1000+次试验的A/B测试平台揭秘

    编者按:本文详细介绍了 Pinterest 内部A/B测试平台的搭建过程,对于无论是有技术能力和资源想要自建A/B测试系统的大公司,还是想在业务中引入第三方A/B测试方法和工具的中小公司都极具参考意义 ...

  6. mybatis关联查询,查询结果多条,却只返回一条记录

    原因是:主表和子表的主键字段相同,可以使用别名!这是因为mybatis的内部实现机制决定的: MyBatis为了降低内存开销,采用ResultHandler逐行读取的JDBC ResultSet结果集 ...

  7. 第一章 Java语言概述

    1.人机交互有两种方法:一种是图形化界面,一种是命令行方式 2.如何打开命令行:开始-在运行命令行中输入cmd 3.常用的DOS命令: dir(directory):列出当前目录下文件及文件夹 md( ...

  8. C# 类型和变量

    C# 中的类型有两种:值类型 (value type) 和引用类型 (reference type).值类型的变量直接包含它们的数据,而引用类型的变量存储对它们的数据的引用,后者称为对象.对于引用类型 ...

  9. 关于AR,你想要的全在这儿了

    定义 增强现实(Augmented Reality,简称AR),是一种实时地计算摄影机影像的位置及角度并加上相应图像的技术,这种技术的目标是在屏幕上把虚拟世界套在现实世界并进行互动.这种技术估计由19 ...

  10. Loadrunner 在controller中运行socket脚本时报错:Abnormal termination, caused by mdrv process termination 的原因和解决方法

    原因: 网上给出的可能的原因大致有两个: 1.  压力负载机器的资源不足(CPU,内存) 2.  分配内存和释放内存的语句不匹配. 并给出了一些解决方案,最开始我以为是加了IP地址的原因,不断尝试增加 ...