Java并发

J.U.C图

一.线程的安全性

当多个线程访问某个类的时候,不管运行环境采用何种方式调度或者这些线程如何交替执行,并且在主调代码中不需要任何额外的同步或者协同,这个类都可以表现出正确的行为,那么这个类就是线程安全的

无状态和竞态条件

无状态:不包含任何域,也不包含任何对其他对象中域的引用

竞态条件(Race Condition):并发编程中,不恰当的执行时序而出现不正确的结果

保证线程安全

  • 复合操作原子性, "先检查,后执行"的延迟初始化;"读取-修改-写入"操作必须是原子的

  • 加锁机制

内置锁,重入

内置锁:synchronized同步代码块,每个java对象都可以用作一个作为同步的锁

内置锁 (Intrinsic Lock) 也叫监视器锁(Monitor Lock)

重入: 内置锁可重入,当某个线程试图获得一个已经由它自己持有的锁,请求会成功

重入多用于在子类继承线程同步的情况,子类改写父类的synchronized方法,在子类中的super方法调用父类的方法。如果锁不能重入的话,子类已经拥有锁,无法访问父类方法,并陷入死锁状态

死锁

死锁现象的描述:

线程A拥有L锁,并想获得R锁的同时,线程B拥有R锁,并想获得L锁,这样的情况称为死锁

死锁的条件:

  • 1,互斥条件,任务使用的资源中至少有一个是不能共享的
  • 2.至少有一个任务必须持有一个资源,且等待的资源是另一个任务所持有的资源
  • 3.资源不能被任务抢占,任务把资源当做普通事件
  • 4.必须有循环等待

JMM

jmm的原子操作

内存间的交互操作

read: 将一个变量从主内存传输到工作内存

load: 将read的值放入工作内存的变量副本

use: 将工作内存的一个变量值传递给执行引擎

assign:把一个执行引擎接受到的值赋值工作内存的变量

store: 把工作内存变量值传递到主内存

read: 把store的得到的值放到主内存变量中

lock: 作用于主内存变量

unlock:

二.对象的共享

内存模型三大特性

可见性

原子性

有序性

可见性

可见性描述:

一个线程修改了共享变量的值,其他线程能立即得知这个修改;

从java内存模型来说,变量修改后将新值同步会主内存,并在变量读取之前从主内存刷新变量值

实现可见性

1.Volatile变量

主要作用:

确保将变量更新操作通知到其他线程

java默认提供的弱同步机制,但是没有提供锁机制

写入volatile变量相当于退出同步代码块

读取volatile变量相当于进入同步代码块

加锁机制保证(可见性,原子性)

volatile (可见性)

语义:

  • java存储模型,不会对volatile执行进行重排序,保证volatile变量操作按照指令的顺序出现
  • volatile 并不保证线程安全,volatile字段不是原子性,想保证线程安全只有加锁
  • volatile 确保可见性 increment的自增操作

原子操作

锁机制问题

多线程竞争条件下,加锁、释放锁都会导致较多的上下文切换,调度延时,性能问题

一个线程持有锁,会导致其他线程挂起

独占锁

独占锁使一种悲观锁,synchronized 就是独占锁

乐观锁

乐观锁, 每次不加锁,假设没有冲突而去完成某项操作,因为冲突失败就重试,直到成功

乐观锁机制基于 CAS(compare and swap) 来实现

整个J.U.C 都是建立在CAS之上

不变性

不可变对象Immutable一定是线程安全的

  • final
  • String,枚举类型
  • 集合,可以使用Collections.unmodifiableMap() 函数进行实现

显示锁

为什么创建一个和内置锁机制非常相似的新的加锁机制?

大多数情况下,内置锁可以很好的完成任务

内置锁在功能上存在一些局限

  • 无法中断一个正在等待获取锁的线程
  • 无法在请求获取一个锁时无限的等待下去
  • 无法实现非阻塞结构的加锁机制

AQS

AbstractQueuedSynchronizer

抽象队列同步器,基本思想是一个同步器

获取锁:判断当前状态是否允许获取锁,是- 获取锁;否- 对于独占锁(阻塞),失败(共享锁),阻塞队列(阻塞线程)

释放锁:修改状态位,如果有线程因为状态位阻塞的话,就唤醒队列中一个或者更多的线程

ReentrantLock

获取锁

  • 该锁没有被其他线程保持,获取锁并立即返回,将锁的保持计数设置为1
  • 当前线程已经保持该锁,保持计数+1,该方法立即返回
  • 该锁被另一个线程保持,禁用当前线程,获得锁之前,一直处于休眠状态

ReentrantLock是可重入锁

公平锁: 获取一个锁是按照请求顺序得到的

Condition

条件变量用来解决 Object wait()/notify()/notifyAll()的难以使用

释放锁

await(),挂起线程,一旦条件满足被唤醒,再次获取锁

Latch 闭锁

CountDownLatch

闭锁的一种实现,这个闭锁的状态是一次性的

CountDown() 实现自减

CyclicBarrier

循环屏障,计数器可以使用reset()来重置

await() 来完成自减

Seamphore

信号量是一个计数信号量

计数器不为0的时候,对线程放行

计数器位0的时候,请求资源的新线程都会被阻塞,包括增加到请求许可的线程,seamphore是不可重入的

请求一个许可,计数器减1

释放一个请求,计数器加1

是实现线程池,请求池的完美结构

ReentrantReadWriteLock

ReentrantLock实现标准的互斥,一次只有一个线程持有锁(独占锁)

ReentrantReadWriteLock

  • 读取锁上,操作使用共享的获取、释放方法
  • 写入锁上,操作使用独占的获取、释放方法

ReadWriteLock 是一个interface接口

多线程开发的良好实践

  • 1.线程名称的命名有意义和区分度
  • 2.缩小同步范围,减少锁争用(能用同步块就不用同步方法)
  • 3.多用同步工具(CountDownLatch,CycliBarrier,Semaphore,Exchanger);少用wait(),notify(),很难实现复杂控制流
  • 4.使用BlockingQueue,用来实现生产者、消费者问题
  • 5.多用并发集合(ConcurrentHashMap),少用同步集合(HashTable)
  • 6.使用本地变量和不可变量来实现线程安全
  • 7.使用线程池而不是直接创建线程,线程的创建代价很高

Java并发必看

Java并发II的更多相关文章

  1. Java 并发 线程的生命周期

    Java 并发 线程的生命周期 @author ixenos 线程的生命周期 线程状态: a)     New 新建 b)     Runnable 可运行 c)     Running 运行 (调用 ...

  2. Java并发编程基础-线程安全问题及JMM(volatile)

    什么情况下应该使用多线程 : 线程出现的目的是什么?解决进程中多任务的实时性问题?其实简单来说,也就是解决“阻塞”的问题,阻塞的意思就是程序运行到某个函数或过程后等待某些事件发生而暂时停止 CPU 占 ...

  3. Java并发编程实战笔记

    如果当多个线程访问同一个可变的状态变量时没有使用合适的同步,那么程序就会出现错误.有三种方式可以修复这个问题: i.不在线程之间共享该状态变量 ii.将状态变量修改为不可变的变量 iii.在访问状态变 ...

  4. 多线程的通信和同步(Java并发编程的艺术--笔记)

    1. 线程间的通信机制 线程之间通信机制有两种: 共享内存.消息传递.   2. Java并发 Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式执行,通信的过程对于程序员来说是完全透 ...

  5. 【Java并发编程实战】----- AQS(四):CLH同步队列

    在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形.其主要从两方面进行了改造:节点的结构与节点等待机制.在结构上引入了头 ...

  6. 【Java并发编程实战】----- AQS(三):阻塞、唤醒:LockSupport

    在上篇博客([Java并发编程实战]----- AQS(二):获取锁.释放锁)中提到,当一个线程加入到CLH队列中时,如果不是头节点是需要判断该节点是否需要挂起:在释放锁后,需要唤醒该线程的继任节点 ...

  7. 【Java并发编程实战】----- AQS(二):获取锁、释放锁

    上篇博客稍微介绍了一下AQS,下面我们来关注下AQS的所获取和锁释放. AQS锁获取 AQS包含如下几个方法: acquire(int arg):以独占模式获取对象,忽略中断. acquireInte ...

  8. 【Java并发编程实战】-----“J.U.C”:CLH队列锁

    在前面介绍的几篇博客中总是提到CLH队列,在AQS中CLH队列是维护一组线程的严格按照FIFO的队列.他能够确保无饥饿,严格的先来先服务的公平性.下图是CLH队列节点的示意图: 在CLH队列的节点QN ...

  9. 【Java并发编程实战】-----“J.U.C”:CountDownlatch

    上篇博文([Java并发编程实战]-----"J.U.C":CyclicBarrier)LZ介绍了CyclicBarrier.CyclicBarrier所描述的是"允许一 ...

随机推荐

  1. JavaScript之预编译

    javascript是一种解释性弱类型语言,在浏览器中执行时,浏览器会先预览某段代码进行语法分析,检查语法的正确与否,然后再进行预编译,到最后才会从上往下一句一句开始执行这段代码,简单得来说可以表示为 ...

  2. linux上Docker安装gogs私服亲测(详解)

    一.前言 有网友问我为什么要使用私服,可能大部分人都不是太懂,网上那么多存储仓库而且好用方便,但是你想过没有如果企业中的项目,放在人家的仓库上这个安全性不是太好,所以说一般企业都会有自己的私服.本章教 ...

  3. Java IO 流 -- 设计模式:装饰设计模式

    在java IO 流中我们经常看到这样的写法: ObjectOutputStream oos = new ObjectOutputStream( new BufferedOutputStream(ne ...

  4. vnpy源码阅读学习(9)回到OptionMaster

    回到OptionMaster 根据我们对APP调用的代码阅读,我们基本上知道了一个APP是如何被调用,那么我们回到OptionMaster学习下这个APP的实现. 看看结构 class OptionM ...

  5. ES6中对函数的扩展

    ES6一路扩展,字符串.数组.数值.对象无一“幸免”,ES6说要雨露均沾,函数也不能落下,今天,就来讲解ES6对函数的扩展. 参数的默认值 在开发中,给函数的参数指定默认值,是很普遍很常见的一个需求, ...

  6. 详解数组分段和最大值最小问题(最小m段和问题)

    数组分段和最大值最小问题(最小m段和问题) 问题描述 给定n个整数组成的序列,现在要求将序列分割为m段,每段子序列中的数在原序列中连续排列.如何分割才能使这m段子序列的和的最大值达到最小? 清洁工:假 ...

  7. PHP 把MYSQL重复ID 二维数组重组为三维数组

    应用场景 MYSQL在使用关联查询时,比如 产品表 与 产品图片表关联,一个产品多张产品图片,关联查询结果如下: $arr=[['id'=>1,'img'=>'img1'],['id'=& ...

  8. centos7与8的区别

    1.关于内核版本:RHEL8采用4.18.0-xRHEL7采用3.10-0-x 2 网络时间同步 RHEL8 只使用Chronyd,不支持NTP部署. RHEL7Chronyd与NTP两者都支持 3. ...

  9. Spring Boot中的测试

    文章目录 简介 添加maven依赖 Repository测试 Service测试 测试Controller @SpringBootTest的集成测试 Spring Boot中的测试 简介 本篇文章我们 ...

  10. c语言----- 冒泡排序 for while do-while 递归练习

    1. 冒泡排序简介(默认从小到大排序) 核心思想:只比较相邻的两个元素,如果满足条件就交换    5 8 2 1 6 9 4 3 7 0 目标:0 1 2 3 4 5 6 7 8 9 第一次排序: 5 ...