线程是CPU调度执行的基本单位,多个线程共享系统为进程分配的资源,又可以被系统独立调度执行。
多线程模型
实现多线程主要有3种模型:内核线程模型、用户线程模型、混合线程模型
- 内核线程模型
内核线程模型即完全依赖操作系统内核提供的内核线程(Kernel-Level Thread ,KLT)来实现多线程。在此模型下,线程的切换调度由系统内核完成,系统内核负责将多个线程执行的任务映射到各个CPU中去执行。
用户进程使用系统内核提供的接口———轻量级进程(Light Weight Process,LWP)来使用系统内核线程。
在此种线程模型下,由于一个用户线程对应一个LWP,因此某个LWP在调用过程中阻塞了不会影响整个进程的执行。
但这种线程模型也有如下缺点:
- 各种线程的操作都需要在用户态和内核态之间频繁切换,消耗较大
- 每个LWP都需要一个内核线程来支持执行用户代码,会消耗更多的内核内存空间,因此系统内核提供的KLT的数量是有限制的。
- **用户线程模型**
用户线程模型完全建立在用户空间的线程库上,不依赖于系统内核,用户线程的创建、同步、切换和销毁等操作完全在用户态执行,不需要切换到内核态。
在此种线程模型下,线程的各种操作以及切换消耗很低,但线程的所有操作都需要在用户态实现,线程的调度实现起来异常复杂,处理器映射更是无法实现。
- **混合线程模型**
混合线程模型是前述两种模型的混合版本,用户线程仍然是在用户态中创建,用户线程的创建、切换和销毁的消耗很低,用户线程的数量不受限制。而LWP在用户线程和内核线程之间充当桥梁,就可以使用操作系统提供的线程调度和处理器映射功能。
当前,Java虚拟机使用的线程模型是基于操作系统提供的原生线程模型来实现的,Windows系统和Linux系统都是使用的内核线程模型,而Solaris系统支持混合线程模型和内核线程模型两种实现。
Java线程内存模型
Java线程内存模型中,可以将虚拟机内存划分为两部分内存:主内存和线程工作内存,主内存是多个线程共享的内存,线程工作内存是每个线程独享的内存。
在上图中,方法区和堆内存就是主内存区域,而虚拟机栈、本地方法栈以及程序计数器则属于每个线程独享的工作内存。
Java内存模型规定所有成员变量都需要存储在主内存中,线程会在其工作内存中保存需要使用的成员变量的拷贝,线程对成员变量的操作(读取和赋值等)都是对其工作内存中的拷贝进行操作。
各个线程之间不能互相访问工作内存,线程间变量的传递需要通过主内存来完成。
Java内存模型定义了8种原子操作来实现上图中的线程内存交互:
- read,将主内存中的一个变量的值读取出来
- load,将read操作读取的变量值存储到工作内存的副本中
- use,把工作内存中的变量的值传递给执行引擎
- assign,把从执行引擎中接收的值赋值给工作内存中的变量
- store,把工作内存中一个变量的值传递到主内存
- write,将store操作传递的值写入到主内存的变量中
- lock,将主内存中的一个变量标识为某个线程独占的锁定状态
- unlock,将主内存中线程独占的一个变量从锁定状态中释放
####原子性、可见性和有序性 - **原子性**
Java内存模型定义了8中原子操作,此外Java内存模型还保证了对于基本数据类型(char、boolean、int等)的操作是原子性的。对于其他类型的数据如若需要更灵活的原子性操作,Java内存模型提供了lock和unlock操作。JVM中使用的两个字节码指令monitorenter和monitorexit即是通过lock和unlock操作来实现的,常使用的synchronized关键字转换成字节码指令后即由monitorenter和monitorexit构成。
- 可见性
可见性是指当一个线程修改了主内存中变量的值,其他线程可以立即获取这个修改后的新值。只要在工作内存中修改变量之后立即存储到主内存,以及读取一个变量之前强制从主内存刷新变量的值即可保证可见性。volatile关键字即通过上述方法保证多线程操作变量时的可见性。
- 有序性
有序性是指在同一个线程中的所有操作都是有序执行的,但由于指令重排序等行为会导致指令执行的顺序不一定是按照代码中的先后顺序执行的,在多线程中对一个变量的操作就可能会受到指令重排序的影响。volatile关键字包含有禁止指令重排序的作用,因此使用volatile关键字修饰的变量可以保证多线程之间对该变量操作的有序性。
参考资料:《深入理解Java虚拟机》