一、进程、线程与任务
进程是程序运行的实例。运行一个java程序的实质是启动一个Java虚拟机进程,也就是说一个运行的Java程序就是一个Java虚拟机进程。
进程是程序向操作系统申请资源的基本单位。线程是进程中可独立执行的最小单位。一个进程可以包含多个线程。同一个进程中的所有线程共享该进程中的资源,如内存空间、文件句柄等。
线程所要完成的计算就被称为任务,特定的线程总是在执行着特定的任务。
二、串行、并发与并行
在单个处理器上采用单核依次执行多个任务,前一个小任务没完成不能操作下一个即为串行。
在单个处理器上采用单核执行多个任务即为并发。在这种情况下,操作系统的任务调度程序会很快从一个任务切换到另一个任务,因此看起来所有的任务都是同时运行的。
同一时间内在不同计算机、处理器或处理器核心上同时运行多个任务,就是所谓的“并行”。
从软件的角度来说,并发就是在一段时间内以交替的方式去完成多个任务,而并行就算以齐头并进的方式去完成多个任务。
从硬件的角度来说,在一个处理器依次只能够运行一个线程的情况下,由于处理器可以使用时间片分配的技术来实现在同一段时间内运行多个线程,因此一个处理器就能实现并发。而并行则需要多个处理器在同一时刻各自运行一个线程来实现。
多线程编程的实质就是将任务的处理方式由串行改为并发,即实现并发化,以发挥并发的优势。
三、竞态
多线程编程中经常遇到一个问题就是对于同样的输入,程序的输出有时候是正确的而又时候却是错误的。这种一个计算结果的正确性与时间有关的现象被称为竞态。
“竞态”为什么会发生?
“竞态”的发生主要是因为多个线程都对一个共享变量有读取-修改的操作。在某个线程读取共享变量之后,进行相关操作的时候,别的线程把这个变量给改了,从而导致结果出现了错误。
什么样的代码模式会发生“竞态”
这部分知识主要是来自《Java多线程编程实战指南 核心篇》。 这里书中提到,会发生竞态条件就是两个模式:read-modify-write(读-改-写)和 check-than-act(检测而后行动)。 当然,这里面的都有一个相同的操作过程:某个有读取这个“共享变量”的操作,然后别的线程有个修改这个变量的操作。这里有个重点,在多个线程中,起码有一个线程有更新操作;如果所有的线程都是读操作,那么就不存在什么竞态条件。 总体来说,就是要thread1#load - thread2#update。 这种的模式,起码是要有两个线程的,而且其中某个线程肯定是要有更新“共享变量”操作的,另一个线程不管是读取变量还是更新变量都会出现错误(要么读取脏数据、要么丢失更新结果)。
如何消除“竞态”?
单以上面的操作来说,一般来说有两种解法方式
- 加锁
- 利用原子操作
四、可见性、原子性、有序性
可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到,我们称为可见性。
原子性:我们把一个或者多个操作在 CPU 执行的过程中不被中断的特性称为原子性。
有序性:编译器为了优化性能,有时候会改变程序中语句的先后顺序。
五、Java是如何解决可见性和原子性问题的
Java 内存模型规范了 JVM 如何提供按需禁用缓存和编译优化的方法。
具体来说,这些方法包括volatile、synchronized、final三个关键字。以及六项Happens-before规则。