第一章
1.1 进程、线程与任务
- 进程是程序向操作系统申请资源(如内存空间和文件句柄)的基本单位。
- 线程是进程中可独立执行的最小单位。
1.2 线程的创建、启动与运行
-
Thread类的start方法的作用是启动相应的线程。启动一个线程的实质是请求Java虚拟机运行相应的线程,而这个线程具体何时运行是由线程调度器(Scheduler)决定的。因此,start方法调用结束并不意味着相应线程已经开始运行,这个线程可能稍后才被运行,甚至也可能永远不会被运行。
-
两种创建Thread方法:
- 定义Thread类的子类,在该子类覆盖run方法
- 创建一个java.lang.Runnable接口的实例,并在该实例的run方法中实现任务处理逻辑,然后以该Runnable接口实例作为构造器的参数直接创建(new)一个Thread类的实例。
-
不管是采用哪种方式创建线程,一旦线程的run方法执行(由java虚拟机调用)结束,相应的线程的运行也就结束了。
-
线程的start方法也只能被调用一次,多少次调用同一个Thread实例的start方法会导致其抛出IllegalThreadStateException。
-
Java虚拟机会为每个线程分配调用栈(Call Stack)所需的内存空间。调用栈用于跟踪Java代码(方法)间的调用关系以及Java代码对本地代码的调用。Java平台中的每个线程可能还有一个内核线程。因此创建线程对象比创建其他类型的对象的成本要高一些。
-
两种线程创建的区别
- 从面向对象编程的角度来讲。第一种基于继承。第二种基于组合。组合相对继承来说,其类和类之间的耦合性(coupling)更低,因此它也更加灵活。
- 从对象共享的角度来说。第二种创建方式意味着多个线程实例可以共享同一个Runnable实例。在某些情况下这可能导致程序的运行结果出乎我们的意料。
- 从对象创建成本的角度来看:创建一个线程实例比创建一个Runnable实例,其成本要相对昂贵一点。如果创建Runnable实例在将其作为方法参数传递给其他对象使用(Java库中有不少API都使用了Runnable)而不必利用它来创建相应的线程即可满足我们的计算需要,那么就不要创建线程实例。
public class TestThread extends Thread{
public void run(){
for(int i = 0;i<3000;i++) {
System.out.println("I love u 3000");
}
}
public static void main(String[] args) {
TestThread testThread = new TestThread();
testThread.start();
}
}
public class Thread_Test implements Runnable {
@Override
public void run() {
for(int i = 0;i<100;i++){
System.out.println("I love u 3000.");
}
}
public static void main(String[] args) {
Thread_Test thread_test = new Thread_Test();
new Thread(thread_test).start();
}
}
-
获取处理器个数
Runtime.getRuntime().availableProcessors();
-
线程属性
- 编号(ID)某个编号的线程运行结束后,该编号可能被后续创建的线程使用。不适合用作某种唯一标识,特别是作为数据库中的唯一标识(如主键)。
- 名称(name)有助于代码调试和问题定位
- 线程类别(Daemon)类型:boolean。值为true表示相应的线程为守护线程。该属性的默认值与相应线程的父线程的该属性的值相同。该属性必须在相应线程启动之前设置。负责一些关键任务处理的线程不适宜设置为守护线程。
- 优先级(Priority)1~10.默认为5。不适当设置该属性值可能导致严重的问题(线程饥饿)。本质上只是一个给线程调度器的提示信息,以便于线程调度器决定优先调度哪些线程运行。它并不能保证线程按照其优先级高低的顺序运行。
-
Thread类的常用方法
- join() 等待相应线程运行结束。若线程A调用线程B的join方法,那么线程A的运行会被暂停,直到线程B运行结束。
- yield() 使当前线程主动放弃其对处理器的占用,这可能导致当前线程被暂停。此方法是不可靠的。该方法被调用时当前线程可能仍然继续运行。执行该方法的线程对线程调度器说:“我现在不急,如果别人需要对处理器资源的话先给他用吧。”
-
web应用中的Servlet类的doGet、doPost等方法也总是由确定的线程负责执行的。Java虚拟机垃圾回收器也是。
1.3 线程的层次关系
- 一个线程是否是一个守护线程默认取决于其父线程。
- 一个线程的优先级默认值为该线程的父线程的优先级。
- 父线程和子线程之间的生命周期也没有必然的联系。比如父线程运行结束后,子线程可以继续运行,子线程运行结束也不妨碍其父线程继续运行。
1.4 线程的生命周期状态

-
Thread.getState()
调用来获取线程状态。- NEW:一个已创建而未启动的线程处于该状态。
- RUNNABLE:该状态可以被看做一个复合状态。包括两个子状态:READY和RUNNING。前者表示处于该状态的线程可以被线程调度器进行调度而使之处于RUNNING状态。执行Thread.yield()的线程,其状态可能会由RUNNING转换为READY。处于READY子状态的线程也别称为活跃线程。
- BLOCKED:一个线程发起一个阻塞式I/O操作后,或者申请一个由其他线程持有的独占资源(比如锁)时,相应的线程会处于该状态。处于BLOCKED状态的线程并不会占用处理器资源。
1.5 多线程编程的优势和风险
- 优势
- 提高系统的吞吐率
- 提高响应性
- 充分利用多核处理器资源
- 最小化对系统资源的使用
- 简化程序的结构
- 缺点
- 线程安全问题
- 线程活性问题
- 上下文切换。处理器从执行一个线程转向执行另外一个线程的时候操作系统所需要做的一个动作被称为上下文切换。
网友评论