Java并发编程实战

最近在看java并发编程实战,打算记录一些自己认为经常面试用的知识点。


1.Volatile变量

1.1 作用:它用来确保将变量的更新操作通知到其他线程。
volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。
1.保证可见性、不保证原子性
2.禁止指令重排序
可见性的实现:
(1)修改volatile变量时会强制将修改后的值刷新的主内存中。
(2)修改volatile变量后会导致其他线程工作内存中对应的变量值失效。因此,再读取该变量值的时候就需要重新从读取主内存中的值。

从汇编角度起步理解实现原理:
volatile变量修饰的共享变量进行写操作的代码转变为汇编代码,会有Lock前缀的指令
Lock前缀指令在多核处理器中会引发两件事:
(1)(volatile写的内存语义)将当前线程的工作内存中关于这个共享变量的数据写回主内存;
(2)(volatile读的内存语义)这个写回主内存的操作会使其他线程的工作内存缓存这个共享变量的数据无效(过期了);
详细说明:
为了提高处理速度,线程一般会直接先从主内存中复制一份到自己的工作内存中,然后在工作内存中进行操作,然后会选择一个时间,将工作内存中的数据写回主内存中(这个时间不确定)。如果声明了volatile的变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,会将这个变量缓存在线程工作内存中的数据写回主内存中。剩下要处理的就是如何将其他线程缓存该变量的值进行更新?在多处理器的情况下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改了,就会将当前处理器的缓存行设置成无状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读取到处理器缓存里。

有序性的实现:
volatile限制操作系统进行指令重排序(编译器重排序和处理器重排序)。而实现volatile可见性和happen-befor的语义是由JVM通过一组叫做内存屏障的处理器指令完成的,它可以实现对内存顺序的限制。
对于原子性:
volatile只能保证读/写操作单个的原子性,如果一个操作包含了读和写之类的复合操作,volatile不能保证其原子性。

1.2 Volatile变量是比锁更加轻量级的一种同步机制。
线程在访问volatile变量的时候不会执行加锁操作,所以也不会执行线程阻塞,因此volatile是一种比synchronized更加轻量级的同步机制。

1.3 volatile变量对可见性的影响比volatile变量本身更加重要。
当线程A首先写入一个volatile变量并且线程B随后读取到了该变量后,对写入volatile变量之前对A可见的所有变量值,在B读取了volatile变量后。对B也是可见的。从内存可见的角度来看,写入volatile变量相当于推出同步代码块,而读取volatile变量就相当于进入同步代码块。

1.4 使用volatile变量的条件
(1)对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值;
(2)该变量不会与其他状态变量一起纳入不变性条件中;
(3)在访问变量时不需要锁。
比如在第一个条件中,遇到要保证递增操作(i++)的原子性时候,就不能只靠使用volatile变量,因为volatile只能确保可见性,不能确保原子性。
对于第二个条件,如果程序中要求保存当前的值和上一个值,如果都用volatile变量修饰,就有两个volatile变量。但是在保存当前的值的时候,不能原子性地保存上一个值,这样就会造成在保存这两个值的间隙可能会被其他线程修改值,产生程序不正常运行。


2.JVM内存模型

这里写图片描述
Java内存模型将内存分为了 主内存和工作内存 。类的状态,也就是类之间共享的变量,是存储在主内存中的,每个线程都有一个自己的工作内存(相当于CPU高级缓冲区,这么做的目的还是在于进一步缩小存储系统与CPU之间速度的差异,提高性能),每次Java线程用到这些主内存中的变量的时候,会读一次主内存中的变量,并让这些内存在自己的工作内存中有一份拷贝,运行自己线程代码的时候,用到这些变量,操作的都是自己工作内存中的那一份。在线程代码执行完毕之后,然后在某个时间点上再将最新的值更新到主内存中去。
这样导致的问题是,如果线程1对某个变量进行了修改,线程2却有可能看不到线程1对共享变量所做的修改。


3.什么是CAS ?

CAS,全称为Compare and Set,即比较-设置。假设有三个操作数: 内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,才会将内存值修改为B并返回true,否则什么都不做并返回false 。当然CAS一定要volatile变量配合,这样才能保证每次拿到的变量是主内存中最新的那个值,否则旧的预期值A对某条线程来说,永远是一个不会变的值A,只要某次CAS操作失败,永远都不可能成功。


4.什么是乐观锁和悲观锁

(1)乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将 比较-设置 这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。

​ 乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。

(2)悲观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,然后再操作资源。


5.多线程中一些方法

5.1Object的方法
1.wait()
方法wait的作用是使当前执行代码的线程进行等待,将当前线程置入“预执行队列”中,并且wait所在的代码停止执行,直到接到通知或被中断。
(在调用wait()方法之前,线程必须获得该对象的对象锁,因此只能在同步方法/同步代码块中调用wait方法。
执行了wait方法后,当前线程会释放锁。)

2.notify()
notify方法的作用是,用来通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待,那么线程等待,则由线程规划器随机挑选出一个wait状态的线程,对其发出通知的notify,并使它等待获取该对象的对象锁
(注意等待获取该对象的对象锁,即使执行了notify方法,当前线程也不会马上释放该对象所,wait状态的线程也不能马上获取该对象锁,要等到执行notify方法的线程将程序执行完,退出synchronized代码块以后,当前线程才会释放锁,wait状态的线程才能获取到该对象锁。
notify方法也必须在同步方法或者同步代码块中使用。
notify方法不立即释放对象锁。)

总结:wait使线程停止运行,而notify使停止的线程继续运行。

3.notifyAll
notifyAll方法可以所有正在等待队列中等待同一共享资源的全部线程,让它们从等待状态,进入可运行状态。

5.2Thread类的实例方法
1.start()
start方法的作用就是通知线程规划器,此线程可以运行了,正在等待CUP调用线程对象。
(start方法执行了,线程不是立即就会执行,需要等待CPU调用。
start方法的执行顺序不代表线程启动的顺序,线程启动的顺序具有不确定性。)

2.run()
线程开始执行,虚拟机调用的是线程run()方法中的内容。
(只有run方法而不调用start方法来启动线程是没有多线程的意义的,只是一个普通方法,属于调用run方法的线程。)

3.isAlive()
该方法的作用是判断当前线程是否处于活动状态。只要线程启动了且没有终止,返回的就是true。

4.getPriority()和setPriority()
这两个方法是获取和设置线程的优先级。
(优先级高的线程更容易被CPU选择执行,得到的CPU资源也更多)

5.interrupt()
该方法的作用并不是中断线程,而是在线程阻塞的时候给线程一个中断标识,表示线程中断。
一般可以与wait()方法一起使用,特别用interru方法打断wait方法,会抛出异常中断线程的阻塞状态。

6.join()
join方法的作用是等待线程对象毁灭。
(模拟场景:主线程中包含一个子线程,子线程执行了,并调用了join方法。
join方法会使调用join方法的线程(也就是子线程)所在的线程(也就是主线程)无限阻塞,直到调用join方法的线程(子线程)毁灭,再继续执行子线程以后的代码。
join方法内部通过使用wait方法实现等待。
join方法执行后,当前线程的锁立即被释放(因为内部使用wait,wait方法执行后会释放锁);sleep方法却不是释放锁)

5.3Thread中的静态方法
1.currentThread()
该方法返回的是对当前正在执行线程对象的引用。比如Thread.currentThread()…..
(线程类的构造方法,静态代码块是被main线程调用,而线程类的run方法才是线程自己调用的)

2.sleep()
该方法的作用是在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行)。
(这个“当前执行的线程”就是指Thread.currentThread()返回的线程。
sleep方法并不释放锁,如果sleep代码上下文如果被加锁了,锁依旧在,但是CPU资源会让给其他线程)

3.yield()
暂停当前的执行的线程对象,并执行其他线程。这个暂停是会放弃CPU资源的,并且放弃的CPU资源的时间不确定,有可能刚放弃,就获得了资源,也有可能很久才会获得CPU资源。


6.线程的状态
线程共有六种状态:
NEW状态,线程实例化后海未执行start()方法时的状态;
RUNNABLE状态,线程进入运行的状态;
BLOCKED状态,线程在等待锁时的状态;
WAITING状态,线程执行了Object.wait()方法的状态,需要其他线程进行如notify的方法才能继续执行;
TIMED_WAITING状态,线程执行了Thread.sleep()方法,呈等待状态,等待时间到达,就可以继续执行;
TERMINATED状态,线程被销毁的状态。


wait释放锁,notify不释放,notifyall是让所有wait的线程状态改为可运行。
interrupt让wait的线程抛出异常,interrupt有点迷惑性,它并不能中断线程,


JMM java内存模型

前 言 第1章 简介 1.1 并发简史 1.2 线程的优势 1.2.1 发挥多处理器的强大能力 1.2.2 建模的简单性 1.2.3 异步事件的简化处理 1.2.4 响应更灵敏的用户界面 1.3 线程带来的风险 1.3.1 安全性问题 1.3.2 活跃性问题 1.3.3 性能问题 1.4 线程无处不在 第一部分 基础知识 第2章 线程安全性 2.1 什么是线程安全性 2.2 原子性 2.2.1 竞态条件 2.2.2 示例:延迟初始化中的竞态条件 2.2.3 复合操作 2.3 加锁机制 2.3.1 内置锁 2.3.2 重入 2.4 用锁来保护状态 2.5 活跃性与性能 第3章 对象的共享 3.1 可见性 3.1.1 失效数据 3.1.2 非原子的64位操作 3.1.3 加锁与可见性 3.1.4 Volatile变量 3.2 发布与逸出 3.3 线程封闭 3.3.1 Ad-hoc线程封闭 3.3.2 栈封闭 3.3.3 ThreadLocal类 3.4 不变性 3.4.1 Final域 3.4.2 示例:使用Volatile类型来发布不可变对象 3.5 安全发布 3.5.1 不正确的发布:正确的对象被破坏 3.5.2  不可变对象与初始化安全性 3.5.3 安全发布的常用模式 3.5.4 事实不可变对象 3.5.5 可变对象 3.5.6 安全地共享对象 第4章 对象的组合 4.1 设计线程安全的类 4.1.1 收集同步需求 4.1.2 依赖状态的操作 4.1.3 状态的所有权 4.2 实例封闭 4.2.1 Java监视器模式 4.2.2 示例:车辆追踪 4.3 线程安全性的委托 4.3.1 示例:基于委托的车辆追踪器 4.3.2 独立的状态变量 4.3.3 当委托失效时 4.3.4 发布底层的状态变量 4.3.5 示例:发布状态的车辆追踪器 4.4 在现有的线程安全类中添加功能 4.4.1 客户端加锁机制 4.4.2 组合 4.5 将同步策略文档化 第5章 基础构建模块 5.1 同步容器类 5.1.1 同步容器类的问题 5.1.2 迭代器与Concurrent-ModificationException 5.1.3 隐藏迭代器 5.2 并发容器 5.2.1 ConcurrentHashMap 5.2.2 额外的原子Map操作 5.2.3 CopyOnWriteArrayList 5.3 阻塞队列和生产者-消费者模式 5.3.1 示例:桌面搜索 5.3.2 串行线程封闭 5.3.3 双端队列与工作密取 5.4 阻塞方法与中断方法 5.5 同步工具类 5.5.1 闭锁 5.5.2 FutureTask 5.5.3 信号量 5.5.4 栅栏 5.6 构建高效且可伸缩的结果缓存 第二部分 结构化并发应用程序 第6章 任务执行 6.1 在线程中执行任务 6.1.1 串行地执行任务 6.1.2 显式地为任务创建线程 6.1.3 无限制创建线程的不足 6.2 Executor框架 6.2.1 示例:基于Executor的Web服务器 6.2.2 执行策略 6.2.3 线程池 6.2.4 Executor的生命周期 6.2.5 延迟任务与周期任务 6.3 找出可利用的并行性 6.3.1 示例:串行的页面渲染器 6.3.2 携带结果的任务Callable与Future 6.3.3 示例:使用Future实现页面渲染器 6.3.4 在异构任务并行化中存在的局限 6.3.5 CompletionService:Executor与BlockingQueue 6.3.6 示例:使用CompletionService实现页面渲染器 6.3.7 为任务设置时限 6.3.8 示例:旅行预定门户网站 第7章 取消与关闭 第8章 线程池的使用 第9章 图形用户界面应用程序 第三部分 活跃性、性能与测试 第10章 避免活跃性危险 第11章 性能与可伸缩性 第12章 并发程序的测试 第四部分 高级主题 第13章 显式锁 第14章 构建自定义的同步工具 第15章 原子变量与非阻塞同步机制 第16章 Java内存模型 附录A 并发性标注
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值