1. 线程、程序、进程的基本概念和联系
1.1 程序
程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。
1.2 进程
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。
1.3 线程
线程与进程相似,但线程是⼀个⽐进程更⼩的执⾏单位。⼀个进程在其执⾏的过程中可以产⽣多个线程。与进程不同的是同类的多个线程共享同⼀块内存空间和⼀组系统资源,所以系统在产⽣⼀个线程,或是在各个线程之间作切换⼯作时,负担要⽐进程⼩得多,也正因为如此,线程也被称为轻量级进程。
2. 线程的基本状态
Java 线程在运⾏的⽣命周期中的指定时刻只可能处于下⾯ 6 种不同状态的其中⼀个状态(图源《Java并发编程艺术》4.1.4 节)。
线程在⽣命周期中并不是固定处于某⼀个状态⽽是随着代码的执⾏在不同状态之间切换。Java 线程状态变迁如下图所示(图源《Java 并发编程艺术》4.1.4 节):
由上图可以看出:
线程创建之后它将处于 NEW(新建) 状态,调用 start()
方法后开始运行,线程这时候处于 READY(可运行) 状态。可运行状态的线程获得了 cpu 时间片(timeslice)后就处于 RUNNING(运行) 状态。
操作系统隐藏 Java 虚拟机(JVM)中的 READY 和 RUNNING 状态,它只能看到 RUNNABLE 状态(图源:HowToDoInJava:Java Thread Life Cycle and Thread States),所以 Java 系统⼀般将这两个状态统称为 RUNNABLE(运行中) 状态 。
当线程执行 wait()
方法之后,线程进入 WAITING(等待)
状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 TIME_WAITING(超时等待)
状态相当于在等待状态的基础上增加了超时限制,比如通过 sleep(long millis)
方法或 wait(long millis)
方法可以将 Java 线程置于 TIMED WAITING
状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE
状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 BLOCKED(阻塞)
状态。线程在执行 Runnable
的run()
方法之后将会进入到 TERMINATED(终止)
状态。
3. final关键字
3.1 final修饰类
用final修饰的类不能被继承,没有子类。
-
例如:我们是无法写一个类去继承String类,然后对String类型扩展的,因为API中已经被String类定义为final的了。
-
我们也可以定义final修饰的类:
3.2 final修饰方法
用final修饰的方法可以被继承,但是不能被子类的重写。
-
例如:每个类都是Object类的子类,继承了Object中的众多方法,在子类中可以重写toString方法、equals方法等,但是不能重写getClass方法 wait方法等,因为这些方法都是使用fianl修饰的。
-
我们也可以定义final修饰的方法:
3.3 final修饰变量
- 用final修饰的变量表示常量,只能被赋一次值,其实使用final修饰的变量也就成了常量了,因为值不会再变了(其实是内存地址不会变)
3.4 小结
final关键字主要用在三个地方:变量、方法、类。
- 对于⼀个 final 变量,如果是基本数据类型的变量,则其数值⼀旦在初始化之后便不能更改;如果是引⽤类型的变量,则在对其初始化之后便不能再让其指向另⼀个对象。(其实都是内存地址不变)
- 当⽤ final 修饰⼀个类时,表明这个类不能被继承。final 类中的所有成员⽅法都会被隐式地指定为 final ⽅法。
- 使⽤ final ⽅法的原因有两个。第⼀个原因是把⽅法锁定,以防任何继承类修改它的含义;第⼆个原因是效率。在早期的 Java 实现版本中,会将 final ⽅法转为内嵌调⽤。但是如果⽅法过于庞⼤,可能看不到内嵌调⽤带来的任何性能提升(现在的 Java 版本已经不需要使⽤ final⽅法进⾏这些优化了)。类中所有的 private ⽅法都隐式地指定为 final。
4. Java 中的异常处理
4.1 Java 异常类层次结构图
在 Java 中,所有的异常都有一个共同的祖先java.lang包中的 Throwable类。
Throwable: 有两个重要的子类:Exception(异常)
和Error(错误)
,二者都是 Java 异常处理的重要子类,各自都包含大量子类。
4.2 Error(错误)
Error(错误)
是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)
出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。
4.3 Exception(异常)
Exception(异常)
是程序本身可以处理的异常。Exception
类有一个重要的子类 RuntimeException
。RuntimeException
异常由Java虚拟机
抛出。NullPointerException
(要访问的变量没有引用任何对象时,抛出该异常)、ArithmeticException
(算术运算异常,一个整数除以0时,抛出该异常)和 ArrayIndexOutOfBoundsException
(下标越界异常)。
注意:异常能被程序本身处理,错误是无法处理。
4.4 Throwable类常用方法
- public string getMessage():返回异常发生时的简要描述。
- public string toString():返回异常发生时的详细信息。
- public string getLocalizedMessage():返回异常对象的本地化信息。使用Throwable的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与getMessage()返回的结果相同。
- public void printStackTrace():在控制台上打印Throwable对象封装的异常信息。
4.5 异常处理总结
- try 块: 用于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。
- catch 块: 用于处理try捕获到的异常。
- finally 块: 无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。
当try语句和finally语句中都有return语句时,在方法返回之前,finally语句的内容将被执行,并且finally语句的返回值将会覆盖原始的返回值。
可以发现,最终都是返回的final代码块的值