为什么需要多线程
CPU,内存,I/O 设备速度是有极大差异的,为了提高CPU的效率,而引入的多线程
为什么会出现线程不安全
在计算机的体系结构设计导致在多线程下程序存在的三个问题:
- CPU的多级缓存机制,导致了可见性问题
- 操作系统的分时复用CPU,即时间片轮转,导致了原子性问题
- 编译器的指令优化执行次序(重排序),使得缓存能够合理利用,导致了有序性问题
上面出现了三个陌生的词汇,可见性、原子性和有序性,如何理解呢? 可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到。 原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。 有序性:即程序执行的顺序按照代码的先后顺序执行。 在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。重排序分三种类型:
- 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
- 指令级并行的重排序。现代处理器采用了**指令级并行技术(Instruction-Level Parallelism, ILP)**来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
- 内存系统的重排序。由于处理器使用缓存和读 / 写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
Java如何解决并发问题
多线程下的不安全问题主要是因为缓存机制和编译优化导致的,而为了解决这一问题,Java的内存模型(JMM)规范了JVM如何禁用禁用缓存和编译优化来保证并发的安全性 具体而言主要包括:
- volatile、synchronized 和 final 三个关键字
- Happens-Before 规则
保证原子性:synchronized 保证有序性:volatile 和 synchronized 、Happens-Before 规则 保证可见性:volatile、synchronized 这里也是常被考察的点volatile能保证原子性吗?答案是不能。
线程安全的实现方法
- 互斥同步: synchronized 和 ReentrantLock
- 非阻塞同步: CAS, AtomicXXXX
- 无同步方案: 栈封闭,Thread Local,可重入代码
互斥同步
互斥同步主要又两种分别是:
非阻塞同步
互斥同步 最主要的问题就是线程阻塞与唤醒所带来的性能问题,因此互斥同步也称为阻塞同步。 互斥同步属于一种悲观的并发策略,总是认为共享数据一定会出现多线程竞争,总是对共享资源加锁。 CAS的全称是比较并交换(Compare-and-Swap,CAS),它是一种乐观的并发策略:先进行操作,如果没有其他线程争用共享资源,那就操作成功,否则采取补偿措施,不断地重试,直到成功为止。因此这种并发策略是不需要阻塞线程的,所以称之为非阻塞同步Java多线程之详解CAS
无同步方案
主要学习ThreadLocal