参考:链接

进程和线程的概念

  • 进程:正在运行的一个程序
  • 线程:进程的一个执行单元,一个线程就是进程中一个单一顺序的控制流,进程的一个执行分支。

进程中至少有一个线程,或多个线程。

每个线程都有自己的线程栈、寄存器环境、线程本地存储。

进程就是应用程序在内存中分配的空间,也就是正在运行的程序,各个进程之间互不干扰。同时进程保存着程序每一个时刻运行的状态。CPU采用时间片轮转的方式运行进程:CPU为每个进程分配一个时间段,称作它的时间片。如果在时间片结束时进程还在运行,则暂停这个进程的运行,并且CPU分配给另一个进程(这个过程叫做上下文切换)。如果进程在时间片结束前阻塞或结束,则CPU立即进行切换,不用等待时间片用完.


进程和线程的区别

本质区别:是否单独占有内存地址空间和其他系统资源(比如I/O)

  • 进程单独占有一定的内存地址空间,所以进程间存在内存隔离,数据是分开的,数据共享复杂但是同步简单,各个进程之间互不干扰;而线程共享所属进程占有的内存地址空间和资源,数据共享简单,但是同步复杂。
  • 进程单独占有一定的内存地址空间,一个进程出现问题不会影响其他进程,不影响主程序的稳定性,可靠性高;一个线程崩溃可能影响整个程序的稳定性,可靠性较低。
  • 进程单独占有一定的内存地址空间,进程的创建和销毁不仅需要保存寄存器和栈信息,还需要资源的分配回收以及页调度,开销较大;线程只需要保存寄存器和栈信息,开销较小。

另一个区别:进程是操作系统进行资源分配的基本单位,线程是操作系统进行调度的基本单位

主线程和子线程

JVM启动时,会创建一个主线程,负责执行main方法。主线程就是main线程。
线程之间不是孤立的,也存在联系。若A线程创建了B线程,A线程就是B线程的父线程,B线程就是A线程的子线程

串行 并发 并行

若有3个任务

  1. 任务A,准备5分重,等待10分钟
  2. 任务B,准备2分钟,等待8分重
  3. 任务C准备10分钟

串行

sequential:所有任务逐个完成,共耗时:15+10+10=35分钟
image.png

并发

concurrent:先做任务A,在任务A等待的过程中,开始做任务B,
image.png

并行

parallel:三个任务同时开始,耗时最小
image.png
并发和并行都可以是很多个线程,就看这些线程能不能同时被(多个)CPU执行,如果可以就说明是并行。而并发是多个线程被(一个)CPU轮流切换着执行。

线程的生命周期

  • NEW 新建状态
    尚未启动的线程处于此状态。在调用start() 启动之前
  • RUNNABLE 可运行状态
    在Java虚拟机中执行的线程处于此状态。复合状态,包含READY和RUNNING状态。(thread.yield()方法可以将线程状态从RUNNING状态转换为READY状态)
    • READY:该状态下,该线程可以被线程调度器进行调度,使他进入RUNNING状态
    • RUNNING:该状态下,表示该线程正在执行。
  • BLOCKED 阻塞状态
    被阻塞等待监视器锁定的线程处于此状态。线程发起阻塞的I/O操作,或者申请由其他线程占用的独占资源。处于阻塞状态的线程,不会占用CPU资源。当阻塞I/O执行完,或者线程获得了其申请的资源,线程可以转换为RUNNABLE状态。
  • WAITING 等待状态
    正在等待另一个线程执行特定动作的线程处于此状态。线程执行了object.wait() 或者 thread.join方法会把线程转换为WATING等待状态,执行object.notify()方法,或者加入线程执行完毕,当前线程会转换为RUNNABLE
  • TIMED_WAITING 超时等待状态
    正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。处于该状态的线程不会无限的等待,如果线程没有在指定二等时间范围内完成期望的操作,线程自动转换为RUNNABLE
  • TERMINATED 中止状态
    已退出的线程处于此状态。

image.png

多线程的问题

线程安全问题

如果内有正确的并发访问控制,可能产生数据一致性问题,例如读取脏数据(过期的数据),如丢失数据更新。

线程活性问题

线程一直处于非RUNNABLE状态
常见的有:

  1. 死锁(deadlock):类似于鹬蚌相争,两个线程都需要对方线程的资源才能执行。
  2. 锁死(lockout):类似于睡美人,王子挂了公主只能一直睡着。
  3. 活锁(livelock):类似于小猫咬自己的尾巴一直咬不到
  4. 饥饿(starvation):类似于健壮的雏鸟总是能够从母鸟最终抢到食物,弱小的雏鸟一直抢不到

上下文切换

处理器从执行一个线程切换到执行另外一个线程。

可靠性

可能由一个线程导致JVM意外终止,其他的线程也无法执行

线程安全问题

非线程安全主要是多个线程对同一个对象的实例变量进行操作时,会出现值被更改,值不同步的情况。
表现为三个方面:原子性、可见性、有序性

原子性

Atomic,不可分割的意思。原子操作不可分割:

  1. 访问(读写)某个共享变量的操作从其他线程来看,该操作要么已经执行完毕,要么尚未发生。即其他线程看不到当前操作的中间结果。
  2. 访问同一组共享变量的原子操作是不能够交错的。

例如从ATM取款

java中有两种方式实现原子性:
一种是使用锁。锁具有排他性,共享变量在某一个时刻只能被一个线程访问。
另一种是使用处理器的CAS指令。

java提供了一个线程安全的AtomicInteger类,保证了操作的原子性

可见性

多线程环境中,一个线程对某个共享变量进行更新后,后续其他的线程可能无法立即读到这个更新的结果,这就是线程安全问题的另一种形式。
如果一个线程对共享变量更新后,后续访问该变量的其他线程可以读取到更新的结果,称这个线程对共享变量的更新对其他线程可见,反之就是不可见。可能使某些线程读取到旧数据。

有序性

有序性是指在什么情况下一个处理器上运行的一个线程所执行的,内存访问操作在另外一个处理器运行的其他线程看来是乱序的。

重排序:

编译器可能会改变两个操作的先后顺序
处理器也可能不会按照目标代码的顺序执行
重排序是对内存访问有序操作的一种优化,可以在不影响单线程程序正确性的情况下提升程序的性能,但是,可能对多线程程序的正确性产生影响。
重排序可以分为:

  • 指令重排序:由JIT编译器处理器引起,指程序顺序与执行顺序不一样
  • 存储子系统重排序:由高速缓存、写缓冲器引起,感知顺序和执行顺序不一致

与内存操作顺序有关的概念

  • 源代码顺序:源码中指定的内存访问顺序
  • 程序顺序:处理器上运行的目标代码所指定的内存访问顺序
  • 执行顺序:内存访问操作在处理器上的实际执行顺序
  • 感知顺序:给定处理器所感知到的该处理器及其他处理器的内存访问操作的顺序