多线程安全问题

本文从操作系统谈起,结合原子性,可见性,代码重排序,阐述多线程的不安全。
目录:
1.OS
2.进程与线程
3.多线程
3.1 什么是多线程
3.2 为什么使用多线程
3.3 多线程的缺点
3.4 理解线程不安全
3.5 保证线程安全的思路

OS(Operating System,简称OS)

一:概念
OS是Operating System的缩写,中文翻译就是操作系统,意思就是计算机管理控制程序,是计算机能够正常运行最基本的系统软件,任何软件都需要OS来支持。
二:作用
1.OS是管理计算机系统的全部硬件资源以及软件资源;
2.控制程序运行;改善人机界面;
3.为其它应用软件提供支持等,使计算机系统所有资源最大限度地发挥作用,为用户提供方便的、有效的、友善的服务界面。

进程与线程

一:进程:进程是系统分配资源的最小单位(OS拥有硬件资源分配权,分配的主体就是进程)
二:线程:线程是系统调度的最小单位(线程是系统分配CPU资源的最小单位)
三:区别及联系:一个进程内的线程之间是可以共享资源的。每个进程至少有一个线程存在,即主线程。

多线程

理解:进程可以简单的理解为一个可以独立运行的程序单位。它是线程的集合,进程就是有一个或多个线程构成的,每一个线程都是进程中的一条执行路径。

一:什么是多线程
多线程就是指一个进程中同时有多个执行路径(线程)正在执行;
二:为什么使用多线程
在一个程序中,有很多的操作是非常耗时的,如数据库读写操作,IO操作等,如果使用单线程,那么程序就必须等待这些操作执行完成之后才能执行其他操作。使用多线程,可以在将耗时任务放在后台继续执行的同时,同时执行其他操作,提高程序的效率。(多线程不保证一定提升程序的效率,要具体情况具体看待)
三:多线程的缺点
1.使用太多线程,是很耗系统资源,因为线程需要开辟内存。更多线程需要更多内存。
2.影响系统性能,因为操作系统需要在线程之间来回切换。
3.需要考虑线程操作对程序的影响,如线程挂起,中止等操作对程序的影响。
4.线程使用不当会发生很多问题。
四:理解线程不安全
1.线程为什么不安全,先了解三个概念:
1.1原子性:原子性就是指一个不可中断的操作,要么全部执行成功要么全部执行失败。在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程所干扰。
我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入房间之后,还没有出来;B 也进入房间,打断 A 在房间里的隐私。这个就是不具备原子性的。那我们应该如何解决这个问题呢?是不是只要给房间加一把锁,A 进去就把门锁上,其他人是不是就进不来了。这样就保证了这段代码的原子性了。有时也把这个现象叫做同步互斥,表示操作是互相排斥的。
不保证原子性会给多线程带来什么问题?
如果一个线程正在对一个变量操作,中途其他线程插入进来了,如果这个操作被打断了,结果就可能是错误的。

1.2可见性:为了提高效率,JVM在执行过程中,会尽可能的将数据在工作内存中执行,但这样会造成一个问题,共享变量在多线程之间不能及时看到改变,这个就是可见性问题。
多线程安全问题
Java 的内存模型
如图所示每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝)。
线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读写;不同线程之间无法直接访问其他线程工作内存中的变量,线程间变量值的传递需要通过主内存来完成。
1.3指令做重排序: 在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序(编译器,JVM,CPU体系都有可能重排序代码)
代码重排序会给多线程带来什么问题
举个例子:
一段代码是这样的:
1. 去前台取下 U 盘
2. 去教室写 10 分钟作业
3. 去前台取下快递
如果是在单线程情况下,JVM、CPU指令集会对其进行优化,比如,按 1->3->2的方式执行,也是没问题,可以少跑一次前台。但在多线程场景下就有问题了,可能快递是在你写作业的10分钟内被另一个线程放过来的,或者被人变过了,如果指令重排序了,代码就会是错误的。
现在我们谈一下多线程为什么不安全
操作系统(如Windows、Linux)的任务调度是采用时间片轮转的抢占式调度方式,也就是说一个任务执行一小段时间后强制暂停去执行下一个任务,每个任务轮流执行。任务执行的一小段时间叫做时间片,任务正在执行时的状态叫运行状态,任务执行一段时间后强制暂停去执行下一个任务,被暂停的任务就处于就绪状态等待下一个属于它的时间片的到来。
换句话说,线程在运行过程中,什么时候被从CPU上切换下来是个概率事件,无法确定,所有排队的线程中,哪个线程会被选择切换到CPU上也是一个概率事件,无法确定。因为这些不确定性,所以多线程无法保证其原子性,可见性,所以多线程是不安全的。

五:保证线程安全的思路
1.是否需要多线程
线程使用不当会带来诸多问题,根据实际情况可以考虑不使用多线程
2.线程是否处理了共享资源,并对共享资源有修改的操作,如果没有,那么不必考虑线程安全的问题,反之,利用多线程的各种机制进行解决(synchronized,volatile等)。