是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。
线程是操作系统能够进行运算调度的最小单位。
它被包含在进程之中,是进程中的实际运作单位。
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。
进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响。
线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。
但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
单线程程序:程序执行过程中只有一个有效操作的序列,不同操作之间都有明确的执行先后顺序,容易出现代码阻塞
多线程程序:有多个线程,线程间独立运行,能有效地避免代码阻塞,并且提高程序的运行性能
(1)使用多线程可以减少程序的响应时间。 在单线程的情况下,如果某个程序很耗时或者陷入长时间等待(如等待网络响应),此时程序将不会相应鼠标和键盘等操作,使用多线程后,可以把这个耗时的线程分配到一个单独的线程去执行,从而是程序具备了更好的交互性。
(2)与进程相比,线程的创建和切换开销更小。 由于启动一个新的线程必须给这个线程分配独立的地址空间,建立许多数据结构来维护线程代码段、数据段等信息,而运行于同一个进程内的线程共享代码段、数据段,线程的启动或切换的开销就比进程要少很多。同时多线程在数据共享方面效率非常高。
(3)多CPU或多核心计算机本身就具有执行多线程的能力。 如果使用单个线程,将无法重复利用计算机资源,造成资源的巨大浪费。因此在多CPU计算机上使用多线程能提高CPU的利用率。
(4)使用多线程能简化程序的结构,使用程序便于理解和维护。 一个非常复杂的进程可以分成多个线程来执行。
当多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替运行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获取正确的结果,那这个对象是线程安全的。 ——<<深入Java虚拟机>>
Java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突。
因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。
检查数值、改变数值,以及可能发生的睡眠操作均作为单一的、不可分割的原子操作完成。
2、进程间的内部数据和状态都是相互完全独立的,因此进程间通信大多数情况是必须通过网络实现。线程本身的数据,通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换比进程切换的负担要小。
3、CPU对于各个线程的调度是随机的(分时调度),在Java程序中,JVM负责线程的调度。 线程调度是指按照特定的机制为多个线程分配CPU的使用权,也就是实际执行的时候是线程,因此CPU调度的最小单位是线程,而资源分配的最小单位是进程。
栈:在函数中定义的基本类型的变量和对象的引用变量都是在函数的栈内存中分配。
堆:堆内存用于存放由new创建的对象和数组。
从通俗化的角度来说,堆是用来存放对象的,栈是用来存放执行程序的
-Xss参数用来控制线程的堆栈大小。
当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。
在临界区中使用适当的同步就可以避免竞态条件。
界区实现方法有两种,一种是用synchronized,一种是用Lock显式锁实现。
不可变的对象一定是线程安全的,并且永远也不需要额外的同步。
Java类库中大多数基本数值类如Integer、String和BigInteger都是不可变的。
由类的规格说明所规定的约束在对象被多个线程访问时仍然有效,不管运行时环境如何排列,线程都不需要任何额外的同步。
如 Random 、ConcurrentHashMap、Concurrent集合、atomic
有条件的线程安全类对于单独的操作可以是线程安全的,但是某些操作序列可能需要外部同步。
有条件线程安全的最常见的例子是遍历由 Hashtable 或者 Vector 或者返回的迭代器
线程兼容类不是线程安全的,但是可以通过正确使用同步而在并发环境中安全地使用。
如ArrayList HashMap
线程对立是那些不管是否采用了同步措施,都不能在多线程环境中并发使用的代码。
如如System.setOut()、System.runFinalizersOnExit()
每一个线程都是有优先级的,一般来说,高优先级的线程在运行时会具有优先权,但这依赖于线程调度的实现,这个实现是和操作系统相关的(OSdependent)。
可以定义线程的优先级,但是这并不能保证高优先级的线程会在低优先级的线程前执行。线程优先级是一个int变量(从1-10),1代表最低优先级,10代表最高优先级。
线程调度器是一个操作系统服务,它负责为Runnable状态的线程分配CPU时间。一旦创建一个线程并启动它,它的执行便依赖于线程调度器的实现。
时间分片是指将可用的CPU时间分配给可用的Runnable线程的过程。分配CPU时间可以基于线程优先级或者线程等待的时间。
线程调度并不受到Java虚拟机控制,所以由应用程序来控制它是更好的选择。
单核CPU也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程时同时执行的,时间片一般是几十毫秒(ms)。
操作系统中,CPU时间分片切换到另一个就绪的线程,则需要保存当前线程的运行的位置,同时需要加载需要恢复线程的环境信息。
守护线程都是为JVM中所有非守护线程的运行提供便利服务: 只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。
因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。
任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon(bool on);true则把该线程设置为守护线程,反之则为用户线程。Thread.setDaemon()必须在Thread.start()之前调用,否则运行时会抛出异常。
守护线程相当于后台管理者 比如 : 进行内存回收,垃圾清理等工作
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种: (一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁) (二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。 (三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
Java提供的终止方法只有一个stop,但是不建议使用此方法,因为它有以下三个问题:
原文:Java架构笔记
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。