Java线程概念

上一篇文章我们讲述了进程与线程概述,接下来的文章我们来探讨Java线程相关的内容。

接下来我们先了解几个概念性的问题

1、什么是Java线程

首先我们明白一个事实就是java是运行在JVM中的,而JVM是操作系统中的一个应用进程。那么如果你看了我的上一遍文章就应该很能够明白我们现在说的Java线程是指JVM会进程中的子任务了。从屏蔽所有方面(比如操作系统等)的细节,可以认为Java线程对应的调度是由JVM来操控的,因此私下可以这么认为,JVM定义了某种线程调度机制,并且在其中定义了有很多的执行队列,比如新建队列(new queue),就绪队列(ready queue),等待队列(waitting queue)等等

2、什么是Java线程组

JVM定义了在多线程运行系统中的线程组(ThreadGroup)对象,用于实现按照特定功能对线程进行集中式分组管理。

用户创建的每个线程均属于某线程组,这个线程组可以在线程创建时指定,也可以不指定线程组以使该线程处于默认的线程组之中。但是,一旦线程加入某线程组,该线程就一直存在于该线程组中直至线程死亡,不能在中途改变线程所属的线程组。 

例如:当Java的Application应用程序运行时,JVM创建名称为main的线程组。除非单独指定,在该应用程序中创建的线程均属于main线程组。在main线程组中可以创建其它名称的线程组并将其它线程加入到该线程组中,依此类推,构成线程和线程组之间的树型管理和继承关系。

3、Java线程有哪些状态(线程的状态控制)

新建(new):当利用new关键字创建线程对象实例后,它仅仅作为一个对象实例存在,JVM没有为其分配CPU时间片等线程运行资源; 

就绪(ready):在处于创建状态的线程中调用start方法将线程的状态转换为就绪状态。这时,线程已经得到除CPU时间之外的其它系统资源,只等JVM的线程调度器按照线程的优先级对该线程进行调度,从而使该线程拥有能够获得CPU时间片的机会。 

运行(running):线程获得了CPU 时间片(timeslice) ,执行程序代码

挂起(suspend):因为某种原因放弃了CPU 使用权,也即让出了CPU时间片,暂时停止运行。这时,线程将释放占用的所有资源,由JVM调度转入临时存储空间,直至应用程序恢复线程运行。

例如:运行(running)的线程执行Object.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。

睡眠(sleep):在线程运行过程中可以调用sleep方法并在方法参数中指定线程的睡眠时间将线程状态转换为睡眠状态。这时,该线程在不释放占用资源的情况下停止运行指定的睡眠时间。时间到达后,线程重新由JVM线程调度器进行调度和管理。 

例如:运行(running)的线程执行Thread.sleep(long ms)或Thread.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。

终止(end):当线程体运行结束后,由JVM收回线程占用的资源时的状态。 

4、Java线程分类

非守护线程:常规的用户线程,用于执行各种各样的任务,可以执行各种各样业务逻辑。

守护线程:这个线程具有最低的优先级,在Java中比较是特殊的线程,用于为系统中的其它对象和线程提供服务。将一个用户线程设置为守护线程的方式是在线程对象创建之前调用线程对象的setDaemon方法。

比如垃圾回收线程就是一个很称职的守护者,它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。

守护线程和非守护线程的区别:唯一的不同之处就在于虚拟机的离开,如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。

5、Java线程之间是如何调度

线程调用的意义在于JVM应对运行的多个线程进行系统级的协调,以避免多个线程争用有限资源而导致应用系统死机或者崩溃。 

为了线程对于操作系统和用户的重要性区分开,JVM定义了线程的优先级策略。JVM将线程的优先级分为10个等级,分别用1-10之间的数字表示。数字越大表明线程的级别越高。相应地,在Thread类中定义了表示线程最低、最高和普通优先级的成员变量MIN_PRIORITY、MAX_PRIORITY和NORMAL_PRIORITY,代表的优先级等级分别为1、10和5。当一个线程对象被创建时,其默认的线程优先级是5。 

为了控制线程的运行策略,JVM定义了线程调度器来监控系统中处于就绪状态的所有线程。线程调度器按照线程的优先级决定哪个线程投入处理器运行。在多个线程处于就绪状态的条件下,具有高优先级的线程会在低优先级线程之前得到执行。线程调度器同样采用"抢占式"策略来调度线程执行,即当前线程执行过程中有较高优先级的线程进入就绪状态,则高优先级的线程立即被调度执行。具有相同优先级的所有线程采用轮转的方式来共同分配CPU时间片。 

在应用程序中设置线程优先级的方法很简单,在创建线程对象之后可以调用线程对象的setPriority方法改变该线程的运行优先级,同样可以调用getPriority方法获取当前线程的优先级。 

6、Java程序启动后,JVM创建了哪些线程

下面是一张java程序启动运行时获取的全部线程的图

参考:Java运行的基本线程解析

Java线程概念_第1张图片

main线程:主线程,JVM创建的第一个线程,属于非daemon线程;

Reference Handler线程:JVM在创建main线程后就创建Reference Handler线程,其优先级最高,为10,它主要用于处理引用对象本身(软引用、弱引用、虚引用)的垃圾回收问题;关于引用,可以参考Java:对象的强、软、弱、虚引用这篇blog;

Finalizer线程:这个线程也是在main线程之后创建的,其优先级为8,主要用于在垃圾收集前,调用对象的finalize()方法;

1) 只有当开始一轮垃圾收集时,才会开始调用finalize()方法;因此并不是所有对象的finalize()方法都会被执行;

2) 该线程也是daemon线程,因此如果虚拟机中没有其他非daemon线程,不管该线程有没有执行完finalize()方法,JVM也会退出;

3) JVM在垃圾收集时会将失去引用的对象包装成Finalizer对象(Reference的实现),并放入ReferenceQueue,由Finalizer线程来处理;最后将该Finalizer对象的引用置为null,由垃圾收集器来回收;

4) JVM为什么要单独用一个线程来执行finalize()方法呢?如果JVM的垃圾收集线程自己来做,很有可能由于在finalize()方法中误操作导致GC线程停止或不可控,这对GC线程来说是一种灾难;

Signal Dispatcher线程:该线程用于处理操作系统发送给的JVM信号;Attach Listener 线程的职责是接收外部jvm命令,当命令接收成功后,会交给signal dispather线程去进行分发到各个不同的模块处理命令,并且返回处理结果。signal dispather线程也是在第一次接收外部jvm命令时,进行初始化工作。

Attach Listener线程:负责接收到外部的命令,而对该命令进行执行的并且把结果返回给发送者。通常我们会用一些命令去要求jvm给我们一些反馈信息,如:java -version、jmap、jstack等等。如果该线程在jvm启动的时候没有初始化,那么,则会在用户第一次执行jvm命令时,得到启动。


你可能感兴趣的:(Java线程概念)