一、为什么学习多线程技术
- 因为并发是整个分布式集群的基础,通过分布式集群不仅可以大大降低同等负载能力的价格,还能使整体可扩展到的负载能力上限大大提升。大多数抽象并发问题的构思与解决都是基于多线程模型来进行的。而且这些并发问题的本质都是相同的,不管是指令级并发、线程并发还是服务器级别的并发都具有类似的特点、面临相似的问题,多线程编程正是我们切入这个领域、学习并发问题解决方案的最好途径。而分布式也有些像是多线程的放大版,所以理解多线程问题的解决思想才是“道”。
二、多线程数据安全问题
-
由来:对性能的追求而导致的编程复杂程度增加。
先看操作系统内存模型如何优化:
1、cpu、内存、I/O 设备三者的速度差异。
解决办法:增加了缓存,以均衡速度差异。
引入新问题:缓存一致性问题(可见性问题)
2、充分利用处理器内部的运算单元 。
解决办法:处理器可能会对代码进行乱序执行优化
引入新问题:指令重排序(一致性问题)
3、处理器需要做别的事 。
解决办法:可切换
引入新问题:返回时共享数据可能已被修改(原子性问题)再看JAVA的“Java内存模型”与“操作系统内存模型”十分类似,也存在这三方面问题,当然这个模型也是JAVA跨平台的基础。
三、JAVA虚拟机提供的基本解决方案
(1)原子性(Atomicity)
Java代码中可用synchronized关键字保证操作的原子性。
(2)可见性(Visibility)
可见性就是指当一个线程修改了共享变量的值时,其他线程能够立即得知这个修改。
Java代码中可用synchronized关键字、volatile变量、final字段保证可见性。
(3)有序性(Ordering)
Java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性,禁止指令重排序。
四、java.util.concurrent提供的高效工具类
-
并发编程可类比成一群人完成一项任务,每个人都是一个线程。这群人之间的关系(按是否协作、是否共享数据)分三种情况。
情况一:不协作,不共享(不互斥)
创建线程,执行各自任务即可。例:在大数据项目中各区县创建一个线程进行数据处理。
情况二:协作,不共享(不互斥)
创建线程,执行各自任务并在合适地方使用同步工具类进行协作。例:在大数据量实时查询时利用Fork/Join框架将线程查询的数据汇集合并。
情况三:协作,共享(互斥)
创建线程,执行各自任务在共享变量处要进行互斥防止出现数据问题。
而JUC包为实现这样的思想提供工具,开发者利用工具做三件事:分解任务、线程协作、共享资源互斥
整个JUC包的内容:
五、举个栗子
目的:应用JMM深入理解为什么类的一般成员变量是不安全的?进一步理解为什么struts2必须是多例的而springmvc是单例的?
在这段代码中count值就是两个线程的共享资源累加后数据小于20000证明是不安全的。
下图JVM实际内存模型:
类比到JMM模型此变量属于主存中内容,所以是不安全的多线程访问需要同步操作。
回忆struts2的数据接收方式是属性驱动或模型驱动,同一个对象这部分数据也是多线程共享的,所以多线程访问需要多例。而springmvc用方法接收参数,方法在虚拟机栈中是线程私有的,所以是单例。内存占用较多可能也是struts2逐渐被springmvc取代的原因。