关于并发编程:关于并发编程与线程安全的思考与实践-京东云技术团队
作者:京东衰弱 张娜 一、并发编程的意义与挑战并发编程的意义是充沛的利用处理器的每一个核,以达到最高的解决性能,能够让程序运行的更快。而处理器也为了进步计算速率,作出了一系列优化,比方: 1、硬件降级:为均衡CPU 内高速存储器和内存之间数量级的速率差,晋升整体性能,引入了多级高速缓存的传统硬件内存架构来解决,带来的问题是,数据同时存在于高速缓存和主内存中,须要解决缓存一致性问题。 2、处理器优化:次要蕴含,编译器重排序、指令级重排序、内存零碎重排序。通过单线程语义、指令级并行重叠执行、缓存区加载存储3种级别的重排序,缩小执行指令,从而进步整体运行速度。带来的问题是,多线程环境里,编译器和CPU指令无奈辨认多个线程之间存在的数据依赖性,影响程序执行后果。 并发编程的益处是微小的,然而要编写一个线程平安并且执行高效的代码,须要治理可变共享状态的操作拜访,思考内存一致性、处理器优化、指令重排序问题。比方咱们应用多线程对同一个对象的值进行操作时会呈现值被更改、值不同步的状况,失去的后果和理论值可能会天差地别,此时该对象就不是线程平安的。而当多个线程拜访某个数据时,不论运行时环境采纳何种调度形式或者这些线程如何交替执行,这个计算逻辑始终都体现出正确的行为,那么称这个对象是线程平安的。因而如何在并发编程中保障线程平安是一个容易疏忽的问题,也是一个不小的挑战。 所以,为什么会有线程平安的问题,首先要明确两个关键问题: 1、线程之间是如何通信的,即线程之间以何种机制来替换信息。 2、线程之间是如何同步的,即程序如何管制不同线程间的产生程序。 二、Java并发编程Java并发采纳了共享内存模型,Java线程之间的通信总是隐式进行的,整个通信过程对程序员齐全通明。 2.1 Java内存模型为了均衡程序员对内存可见性尽可能高(对编译器和解决的束缚就多)和进步计算性能(尽可能少束缚编译器处理器)之间的关系,JAVA定义了Java内存模型(Java Memory Model,JMM),约定只有不扭转程序执行后果,编译器和处理器怎么优化都行。所以,JMM次要解决的问题是,通过制订线程间通信标准,提供内存可见性保障。 JMM构造如下图所示: 以此看来,线程内创立的局部变量、办法定义参数等只在线程内应用不会有并发问题,对于共享变量,JMM规定了一个线程如何和何时能够看到由其余线程批改过后的共享变量的值,以及在必须时如何同步的访问共享变量。 为管制工作内存和主内存的交互,定义了以下标准: •所有的变量都存储在主内存(Main Memory)中。 •每个线程都有一个公有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的拷贝正本。 •线程对变量的所有操作都必须在本地内存中进行,而不能间接读写主内存。 •不同的线程之间无奈间接拜访对方本地内存中的变量。 具体实现上定义了八种操作: 1.lock:作用于主内存,把变量标识为线程独占状态。 2.unlock:作用于主内存,解除独占状态。 3.read:作用主内存,把一个变量的值从主内存传输到线程的工作内存。 4.load:作用于工作内存,把read操作传过来的变量值放入工作内存的变量正本中。 5.use:作用工作内存,把工作内存当中的一个变量值传给执行引擎。 6.assign:作用工作内存,把一个从执行引擎接管到的值赋值给工作内存的变量。 7.store:作用于工作内存的变量,把工作内存的一个变量的值传送到主内存中。 8.write:作用于主内存的变量,把store操作传来的变量的值放入主内存的变量中。 这些操作都满足以下准则: •不容许read和load、store和write操作之一独自呈现。 •对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。 2.2 Java中的并发关键字Java基于以上规定提供了volatile、synchronized等关键字来保障线程平安,基本原理是从限度处理器优化和应用内存屏障两方面解决并发问题。如果是变量级别,应用volatile申明任何类型变量,同根本数据类型变量、援用类型变量一样具备原子性;如果利用场景须要一个更大范畴的原子性保障,须要应用同步块技术。Java内存模型提供了lock和unlock操作来满足这种需要。虚拟机提供了字节码指令monitorenter和monitorexist来隐式地应用这两个操作,这两个字节码指令反映到Java代码中就是同步块-synchronized关键字。 这两个字的作用:volatile仅保障对单个volatile变量的读/写具备原子性,而锁的互斥执行的个性能够确保整个临界区代码的执行具备原子性。在性能上,锁比volatile更弱小,在可伸缩性和执行性能上,volatile更有劣势。 2.3 Java中的并发容器与工具类2.3.1 CopyOnWriteArrayListCopyOnWriteArrayList在操作元素时会加可重入锁,一次来保障写操作是线程平安的,然而每次增加删除元素就须要复制一份新数组,对空间有较大的节约。 public E get(int index) { return get(getArray(), index); } public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); } }2.3.2 Collections.synchronizedList(new ArrayList<>());这种形式是在 List的操作外包加了一层synchronize同步控制。须要留神的是在遍历List是还得再手动做整体的同步控制。 ...