处理器架构
主要有两种选择:单个多核处理器和多个单核处理器。
核心
处理器核心是CPU重要组成部分。处理器所有的计算,接受或存储命令,处理数据都由核心执行。
核心不等价于内核。内核是一种程序,而处理器核心是硬件。
内核是操作系统最基本的部分。它是为众多应用程序提供对计算机硬件的安全访问的一部分软件,执行了应用软件和计算机硬件交互工作。
误区
单个多核处理器,并不是真正意义上同时执行多线程。实际上,这些核心可以分别独立的运行程序指令,利用并行计算的能力加快程序的运行速度。
并行计算,一般是指许多指令得以同时进行的计算模式。在同时进行的前提下,可以将计算的过程分解成小部分,之后以并发方式来加以解决。
多任务处理
多任务处理,指的是计算机同时运行多个程序的能力。
在给定的时间内,每一个处理器只能有一个线程得到真正的运行。所以对于单个处理器而言,多任务处理一般机制是,多个线程合理分配处理器时间。由于线程间切换时间短,给用户造成多个线程同时运行的假象。
而多个单核处理器架构,实现了同时运行多个线程。
每一个线程执行一个任务,而这些线程通常被内核合理分配到处理器上,执行任务。
参考
线程与进程
操作系统必须为进程分配资源,使进程间可以交换信息,保护各个进程的资源不被其他进程占用,并且使进程可以同步。
由此操作系统为进程维护一个数据结构,这个数据结构描述进程的状态和资源所有权,利用此数据结构对进程进行控制。
进程
操作系统为了有序的管理多个程序的执行(调度)和资源分配,提出了进程的概念。
进程的两个基本的元素:程序代码和代码相关联的数据集。而且能分配给处理器并有处理器执行的实体(执行/调度)。
注意进程并不代表一个正在执行的程序。它有可能是多个正在执行的程序的执行流(线程)集合。
总结
进程包含两个特点:
- 资源所有权,包括了一个进程映像(程序、数据、栈和进程控制块的集合)和对资源(包括内存、I/O通道、I/O设备和文件)的访问。
- 调度/执行,一个进程沿着一个或多个程序的一条执行轨迹(线程)执行。并且是一个可被操作系统调度和分派的实体。
线程
针对进程的两大特点,且它们互相独立,派生出一个概念,线程。
供操作系统分派的单位通常是线程(又称轻量级进程),而拥有资源所有权的单位通常是进程(又称任务)。
在一个进程中至少有一个线程,每一个线程有:
- 线程执行状态(运行,就绪等)。
- 在为运行时保存线程的上下文,以便恢复时,快速找到停止的位置(类似程序计数器)。
- 一个执行栈
- 用于每个线程局部变量的静态存储空间。
- 与进程内的其他线程共享着进程的内存和资源的访问。
进程与线程的比较
- 在一个已有进程中创建一个新线程比创建一个全新的进程所花的时间要少。
- 终止一个线程比终止一个进程花费时间少。
- 同一进程内线程间切换比进程间切换花费时间少。
- 线程提高了不同的执行程序间通信效率。
Java并发
由于Java运行时环境(虚拟机)属于单进程多线程,所以针对Java并发编程主要涉及多线程。
多线程是指,单进程内支持多个并发执行路径的能力。
而一个计算机操作系统,例如Windows,使用多进程,且每个进程支持多线程。
在多线程环境中,进程保留了资源的所有权,而多个并发执行流就是执行在进程中运行的线程。
多线程
进程中的所有线程共享该进程的状态和资源,它们驻留在同一块地址空间中,并且可以访问到相同的数据。
例如当一条线程改变了内存中的一个数据项,其他线程在访问这一数据项时能够看到变化后的结果。
线程通信
由于线程共享进程中的资源,包括内存和打开的文件,所以线程间的通信尤为重要。根据线程访问数据次序,可能会造成讹误的对象,这样一个情况通常称为竞争条件。
例如,线程A,B分别调用同一个对象,并且都修改该对象状态。但是利用时间切片进行多线程操作时,A、B线程分别控制处理器修改对象状态,导致实际结果与理论结果不一致。这就是竞争条件。
当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。导致竞态条件发生的代码区称作临界区。
导致竞争条件是因为A(B)线程不是原子操作。
原子操作,指不会被线程调度机制打断的操作。这种操作一旦开始,就一直运行到结束,中间不会有任何切换到另一个线程的操作。
而线程的通信就是为了解决这一问题,同步是线程之间制约关系,是一种重要的通信手段。
以下几个概念参考自临界区,互斥量,信号量,事件的区别、进程与线程;同步与互斥:事件,信号量,临界区,互斥量
线程临界区
保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许一个线程对共享资源进行访问。
如果有多个线程试图同时访问临界区,那么当有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子操作,共享资源的目的。
信号量
信号量对象允许多个线程同时使用共享资源。它指出了同时访问共享资源线程的最大数目。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。
互斥量
互斥量跟临界区很相似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。
当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。互斥量比临界区复杂。因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。
看似和临界区类似,但它们有很大区别。互斥量可以跨越进程使用,所以创建互斥量需要的资源更多。如果只为了在进程内部是用的话使用临界区会带来速度上的优势并能够减少资源占用量(Java涉及的就是临界区操作)。
事件
事件对象也可以通过通知操作的方式来保持线程的同步。并且可以实现不同进程中的线程同步操作。
小结
事件、互斥量、信号量都是内核对象,可以用于线程间或进程间同步,都可以被跨越进程使用来进行同步数据操作。
数据同步则是研究如何保证在一个变量的多个副本之间保持一致性的问题。
- 事件,用于通知其他线程事件已触发,启动线程。
- 信号量,可以对资源进行计数,允许多个线程共享资源,限制了同一时刻多个线程能访问的最大资源数。
- 临界区:在任一时刻仅允许一个线程使用临界区资源。四者中仅有临界区不是内核对象。适合于数据访问的控制。
- 互斥量:拥有互斥量才可以访问共享资源,因此共享资源不会被多个线程所共享。
主线程&子线程
主线程是由Java虚拟机在启动时创建的。main方法执行时,主线程已经创建并在运行中。对于运行中的线程不能设置为守护线程。
用户线程与守护线程
Java中有两类线程:User Thread(用户线程)和Daemon Thread(守护线程)。
用户线程运行在前台,而守护线程运行在后台。
Daemon Thread的作用是为其它线程的运行提供便利服务,守护线程最典型的应用就是GC(垃圾回收)。
User Thread和Daemon Thread的区别就是当虚拟机中仅剩Daemon Thread时,无论Daemon Thread是否执行完毕虚拟机都会退出。
守护线程优先级比较低。
设置守护线程注意点
- 调用
thread.setDaemon(true)
必须在thread.start()
之前,否则抛出异常。 - 在Daemon Thread中创建的新线程默认是守护的。
- 一些耗时的操作逻辑不应该在守护线程中执行,防止虚拟机退出时,执行未完成。
主线程与子线程区别
主线程是一个普通的非守护线程,用来启动程序,同时不可以在代码中设置为守护线程。
Thread.currentThread().setDaemon(true);
除了上述的,主线程与子线程没有任何区别。即使主线程执行完,其他非守护线程一样可以正常执行。
但是如果Java虚拟机中没有非守护线程存活,那么Java虚拟机就会退出。从而所有线程被结束。
线程依赖于进程。只要进程还在,线程就不会被终结。