多线程模型
用户级线程
用户级线程由应用程序通过线程库实现,所有线程管理工作都由应用程序负责。
用户级线程对用户不透明,对操作系统透明。
用户级线程即为从“用户视角可以看到的线程”。
内核级线程
内核级线程的管理工作由操作系统内核完成。线程调度、切换等工作都由内核来负责,因此内核级线程的切换必然需要在核心态下才能完成。
因此在同时支持用户级线程和内核级线程的系统中,可以采用二者组合的方式:将n个用户级线程映射道m个内核级线程上(n>=m)
多对一模型
多个用户级线程映射到一个内核级线程。每个用户进程只对应一个内核级线程。
- 优点:用户级线程的切换在用户空间即可完成,不需要切换到核心态,线程管理的系统开销小,效率高。
- 缺点:当一个用户级线程被阻塞后,整个进程都会被阻塞,并发度不高。多个线程不可再多核处理机并行运行。
一对一模型
一个用户级线程映射到一个内核级线程。每个用户进程有与用户级线程同数量的内核级线程。
- 优点:当一个线程被阻塞后,别的线程还可以继续执行,并发能力强。多线程可在多核处理机上并行执行。
- 缺点:一个用户进程会占用多个内核级线程,线程切换由操作系统内核完成,需要切换到核心态,因此线程管理的成本高,开销大。
多对多模型
n用户级线程映射到m个内核级线程(n>=m)。每个用户进程对应m个内核级线程。
- 优点:克服了多对一模型并发度不高的缺点,又克服了一对一模型中一个用户进程占用太多内核级线程,开销太大的缺点。
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关键字修饰的变量可以保证多线程之间对该变量操作的有序性。