引言
多线程可以并行的处理大量的数据,提高程序的效率
为了这个目标,定义了问题,也遇到了问题,相关概念也应用而生
并发编程模型的关键问题
线程通信:交换信息机制,分为 共享内存(隐式通信)和消息传递(显式通信)
线程同步:控制操作发生相对顺序机制,分为 共享内存(显式同步)和消息传递(隐式同步)
重排序
重排序指编译器和处理器为了优化程序性能而对指令序列进行重新排列的一种手段。
- 数据依赖性:编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序;
- as-if-serial:不管怎么改变,单线程的执行结果不能被改变;
volatile
volatile是轻量级的synchronize,在多处理器开发中保证了共享变量的“可见性”,可以实现一个线程修改一个共享变量,其它线程可以读到这个修改的值,不会引起线程的上下文切换和调度。
实现原则:
- Lock前缀指令会引起处理器缓存会写到内存;
- 一个处理器的缓存回写会导致其它处理器的缓存无效;
特性:
- 可见性:对一个volatile变量的读,总能看到这个volatile变量的最后写入;
- 原子性:对任何单个volatile变量的读/写具有原子性,复合操作不具有(如:volatile++);
内存语义:
- 写:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存;
- 读:当读一个volatile变量是,JMM会把该线程对应的本地内存置为无效,该线程从住内存中读取共享变量
synchronized
Java中的每一个对象都可以作为锁,具体表现为:
- 普通同步方法:锁为当前实例对象;
- 静态同步方法:锁为当前类Class对象;
- 同步方法块:锁为Synchronized括号里的配置的对象;
当一个线程访问同步代码块时,首先必须得到锁,退出或抛出异常时必须释放锁
锁的内存语义:
- 释放锁:JMM把该线程对应的本地内存中的共享变量刷新到主内存中;
- 获取锁:JMM把该线程对应的本地内存置为无效,从而使被监视器保护的临界区代码必须从主内存获取共享变量;
final
final域重排序规则:
- 构造函数对一个final域的写入,然后把引用赋值给引用变量,这两个操作不能重排序;
- 初次读包含final域的对象的引用,与随后初次读final域,这两个操作不能重排序;
原子操作
原子操作:不可被中断的一个或一系列操作
处理器实现:
- 总线锁:一个处理器在总线输出LOCK#信号,其它处理器的请求被阻塞
- 缓存锁:一个CPU修改缓存行使用了缓存锁定,其它CPU就不能进行缓存
Java实现:
- 循环CAS:需要操作时,检查值有没有发生变化;
- 锁机制:只有获得锁的线程才能操作锁定的内存区域;
减少上下文切换
- 无锁并发编程:数据分段,不同的线程处理不同的数据
- CAS算法:使用CAS算法更新数据,无须加锁
- 使用最少线程:避免大量线程处于等待
- 协程:单线程内实现并维持多任务调度和切换
避免死锁
- 避免一个线程获取多个锁
- 尽量一个锁只占用一个资源
- 使用定时锁,lock.tryLock(timeout)代替内部锁机制
- 数据库锁的加锁和解锁要在一个连接中
小结
单线程效率低,所以用多线程;
多线程需要组织管理,所以有了通信和同步;
但是虚拟机和处理器对代码进行了优化,改变了执行顺序,在多线程中是个问题;
所以定义了synchronized、volatile、各种锁来限制关键步骤的顺序一致性,从而保证结果一致;
最后,多线程的使用也是需要消耗资源,所以要减少上下文切换,同时避免死锁;