1、并发编程的挑战上下文切换:CPU通过时间片分配算法来循环执行任务,在切换任务的过程中,会保存上一个任务的状态,以便在下次切换回这个任务时,可以再加载这个任务的状态。
减少上下文切换的方法:无锁并发编程、CAS算法、使用最少线程和使用协程2、Java并发机制的底层实现原理Java代码编译后java字节码然后加载到JVM然后转化为CUP执行的汇编,java的并发依赖于JVM的实现与CPU的指令。
1. Volatile的应用可见性:当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。
后面还是详细介绍volatile关键字2. synchronized的实现原理与应用1) synchronized简介synchronized在JVM中实现,JVM基于进入与退出Monitor对象来实现方法同步与代码块同步,在同步代码块前后分别形成monitorenter和monitorexit这两个字节码。
synchronized的锁存放在java对象头里,在对象头里有关于锁的信息:轻量级锁,重量级锁,偏向锁。
(对象头里还包括:GC标记、分代年龄、线程ID、HashCode等。
)2) 锁的介绍级别从低到高:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,锁能升级不能降级,目的是提高获取锁和释放锁的效率。
偏向锁:在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得。
为了让线程获得锁的代价更低而引入了偏向锁。
当一个线程访问同步块并获取锁(对象)时,会在对象头里记录偏向锁的线程ID。
以后该线程进入与退出同步块时不需要进行CAS操作来加锁和解锁。
如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会尝试消除它身上的偏向锁,将锁恢复到标准的轻量级锁。
轻量级锁:线程通过CAS来获取锁(线程栈帧中有存储锁记录的空间,将Mask Word复制到锁记录中,然后尝试使用CAS将对象头中的Mask Word替换成指向锁记录的指针),如果成功,就获取锁,失败就尝试自旋来获取锁。
重量级锁:为了避免在轻量级中无用的自旋(比如获取到锁的线程被阻塞住了),JVM可以将锁升级成重量级。
当锁处于这个状态时,其他线程试图获取锁时,都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程。
锁优点缺点使用场景偏向锁加锁与解锁不需要额外的消耗。
线程存在竞争时,会带来额外的锁撤销的消耗适用于只有一个线程访问同步块轻量级锁竞争的线程不会阻塞,提高了程序的响应速度始终得不到锁竞争的线程,自旋消耗CPU追求响应时间,同步块执行速度非常快重量级锁线程竞争不使用自旋,不会消耗CPU 线程阻塞,响应时间缓慢追求吞吐量,同步块执行时间较长3. 原子操作的实现原理原子:不能被中断的一个或一系列操作。
在java中可以通过锁和循环CAS的方式来实现原子操作。
1) 使用循环CAS实现原子操作利用处理器提供的CAS指令来实现,自旋CAS现在的基本思路就是循环进行CAS操作直达成功为止。
CAS实现原子操作的三大问题:1. ABA问题:如果一个值原来是A后来变成了B,然后又变成了A。
那么使用CAS时进行检查的时候,该值实际上发送了变化。
解决:加入版本号,如:1A-2B-3A2. 循环时间长开销大:自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。
3. 只能保证一个共享变量的原子操作。
CAS不能对多个共享变量进行。
JDK1.5,提供的AtomicReference类可以保证引用对象之间的原子性,可以将多个变量放在一个对象中进行。
2) 使用锁机制实现原子操作锁机制保证了只有获取到锁的线程才能操作锁定的内存区域。
3、Java内存模型1. Java内存模型的基础1) 并发编程模型的两个关键问题:线程之间的通信与同步。
在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递。
在共享内存的并发模型中,线程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信,在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过发送消息来进行显示通信。
同步是指程序中用于控制不同线程间操作发生相对顺序的机制。
在共享内存模型下,同步是显示进行,程序员必须显示指定某个方法或某段代码需要在线程之间互斥执行。
在消息传递的并发模型里,由于消息的发送必须在消息接收之前,因此同步是隐式进行的。
Java的并发采用的是共享内存模型,java线程之间的通信总是隐式的进行,整个通信过程对程序员透明。
2) Java内存模型的抽象结构Java内存模型规定所有的变量都是存在主存当中,每个线程都有自己的工作内存。
线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。
并且每个线程不能访问其他线程的工作内存。
3) 从源代码到指令序列的重排序源代码-编译期优化重排序-指令级并行重排序-内存系统重排序-最终执行的指令序列在执行程序时,为了提高性能,编译期和处理器常常会对指令做重排序。
可以加入内存屏障禁止指令重排序。
4) Happens-before介绍1. 程序次序规则:一个单线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作2. 锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作3. volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作4. 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C5. 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作6. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生7. 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行8. 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始Happens-before并不意味着前一个操作必须要在后一个操作之前执行,而是要求前一个操作(执行结果)对后一个操作可见。
(若两个操作之间没有依赖,那么可以重排序两个操作)5) as-if-serial语义不管怎么重排序,单线程的执行结果不能被改变。
2. 并发编程的三个重要概念1) 原子性即一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。
2) 可见性可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
对于可见性,Java提供了volatile关键字来保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
3) 有序性即程序执行的顺序按照代码的先后顺序执行。
在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为happens-before 原则。
3. 深入理解volatile关键字1) Volatile的介绍一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:1. 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2. 通过内存屏障,禁止进行指令重排序优化。
volatile变量在每次被线程访问时,都强迫从主内存中重读该变量的值,而当该变量发生变化时,又会强迫线程将最新的值刷新到主内存中。
Volatile的写-读与锁的释放-获取具有相同的内存语义。
Volatile可以保证可见性,不能保证原子性(最经典的是volatile变量的自增操作,在多线程中出现结果错误),能保证一定程度上的有序性。
关于volatile的有序性,volatile关键字禁止指令重排序有两层意思: 1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行; 2)在进行指令优化时,不能将在对volatile变量前面的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
2) volatile的原理和实现机制—加入内存屏障 “观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令” lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能: 1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成; 2)它会强制将对缓存的修改操作立即写入主存; 3)如果是写操作,它会导致其他CPU中对应的缓存行无效。
3) Volatile的运用场景 synchronized关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率;而volatile关键字在某些情况下性能要优于synchronized,但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。
通常来说,使用volatile 必须具备以下2个条件: 1)对变量的写操作不依赖于当前值(自增) 2)该变量没有包含在具有其他变量的不变式中(不懂)可以用作状态标记量volatile boolean flag = false;while(!flag){doSomething();}public void setFlag() {flag = true;}单列模式中double checkclass Singleton{private volatile static Singleton instance = null;private Singleton() {}public static Singleton getInstance() {if(instance==null) {synchronized (Singleton.class) {if(instance==null)instance = new Singleton();}}return instance;}}4. 锁的内存语义锁的实现主要是用了一个volatile的状态变量,这部分放在锁的实现来讲。