进程是资源分配的基本单位。
进程控制块 (Process Control Block, PCB) 描述进程的基本信息和运行状态,所谓的创建进程和撤销进程,都是指对 PCB 的操作。
下图显示了 4 个程序创建了 4 个进程,这 4 个进程可以并发地执行。
线程是 CPU 调度的基本单位。
一个进程中可以有多个线程,它们共享进程资源。
QQ 和浏览器是两个进程,浏览器进程里面有很多线程,例如 HTTP 请求线程、事件响应线程、渲染线程等等,线程的并发执行使得在浏览器中点击一个新链接从而发起 HTTP 请求时,浏览器还可以响应用户的其它事件。
-
资源
进程是资源调度的基本单位。
线程不占有任何资源。
-
调度
线程是独立调度的基本单位。
同一进程中,线程的切换不会引起进程切换,但一个进程中线程切换到另一个进程中的线程时,就会引起进程切换了。
-
系统开销
进程的创建和撤销所付出的开销比线程的创建和撤销要大很多。
类似的,进程切换的开销也比线程切换的开销要大很多,因为进程切换时,涉及到当前执行进程的 CPU 环境的保存和新调度进程 CPU 环境的设置,而线程切换时只需要设置和保存少量的寄存器内容,开销很小。
-
通信
线程可以通过直接读写同一进程中的数据进行通信。
进程之间的通信需要借助 IPC (InterProcess Communication)。
多进程的目的是提高 CPU 的使用率。
多线程的目的是提高应用程序的使用率。
多线程共享同一进程资源(堆内存和方法区),但是栈内存是独立的,JVM 中一个线程对应一个线程栈。多线程抢夺的是 CPU 资源,一个时间点上只有一个线程运行。
-
并发:一个处理器同时处理多个任务,是逻辑上的同时发生。
-
并行:多个处理器或者多核处理器同时处理多个不同的任务,是物理上的同时发生。
来个比喻:并发是一个人同时吃三馒头,而并行就是三个人同时吃三个馒头。
下图反映了一个包含 8 个操作的任务在一个有两核心的 CPU 中创建四个线程运行的情况。
假设每个核心有两个线程,那么每个CPU中两个线程会交替并发,两个CPU之间的操作会并行运算。
单就一个 CPU 而言两个线程可以解决线程阻塞造成的不流畅问题,其本身运行效率并没有提高, 多 CPU 的并行运算才真正解决了运行效率问题,这也正是并发和并行的区别。
Java 有 3 种创建线程的方式:
-
继承 Thread 类,重写 run() 方法
public class MyThread extends Thread{ private void attack() { System.out.println("Fight"); System.out.println("Current Thread is : " + Thread.currentThread().getName()); } @Override public void run() { //重写 run() 方法 attack(); } }
-
实现 Runnable 接口,重写 run() 方法
-
实现 Callable 接口,重写 call() 方法
实现接口会更好一些:
-
避免单继承带来的局限:
Java 不支持多重继承,因此继承了 Thread 类就无法继承其它类,但是可以实现多个接口;
-
代码能够被多个线程共享
-
适合多个相同程序的线程处理同一资源的情况
-
使用接口实现的线程可以对线程进行复用(线程池中创建的线程就是实现 Rubnnable 接口的):
Runnable 是轻量级的,重复 new 不会耗费太多资源。
Thread 是重量级的,并且线程执行结束完后就销毁了,无法再次利用。
注意:一个线程只能 start 一次,如果多次 start,会出现 java.lang.IllegalException
。
-
Callable 的实现方法是 call()
Runnable 的实现方法是 run()
-
Callable 任务执行后有返回值。Callable 与 Future 配合使用,运行 Callable 任务和获取一个 Future 对象 Future 表示异步计算后的结果;
Runnable 任务执行后是没有返回值的
-
Callable 的 call() 方法可以抛出异常
Runnable 的 run() 方法不能抛出异常
如果未获取到值,则主线程等待,一直到获取值为止。
public class CycleWait implements Runnable{
private String value;
@Override
public void run() {
try {
Thread.currentThread().sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
value = "we have data now";
}
public static void main(String[] args) throws InterruptedException {
CycleWait cycleWait = new CycleWait();
Thread t = new Thread(cycleWait);
t.start();
//TODO:如果未获取到值,则主线程等待,一直到获取值为止。
while (cycleWait.value == null){ //主线程等待法
Thread.sleep(1000);
}
System.out.println("value:"+cycleWait.value);
//输出结果:
//value:we have data now
}
}
使用 Thread 的 join() 阻塞当前线程以等待子线程处理完毕。
public class CycleWait2 implements Runnable{
private String value;
@Override
public void run() {
try {
Thread.currentThread().sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
value = "we have data now";
}
public static void main(String[] args) throws InterruptedException {
CycleWait2 cycleWait = new CycleWait2();
Thread t = new Thread(cycleWait);
t.start();
//TODO:使用 Thread 的 join 方法阻塞当前线程,等待子线程执行完毕
t.join(); //阻塞当前主线程,直到 t 线程执行完毕
System.out.println("value:"+cycleWait.value);
//输出结果:
//value:we have data now
}
}
实现 Callablee 接口,使用 FutureTask 接收 call() 方法返回的数据。
public class CycleWait3 implements Callable<String>{
private String value;
@Override
public String call() throws Exception {
try {
Thread.currentThread().sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
value = "we have data now";
return value;
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
CycleWait3 cycleWait = new CycleWait3();
FutureTask ft = new FutureTask(cycleWait);
Thread t = new Thread(ft);
t.start();
while (!ft.isDone()){
Thread.sleep(1000);
}
// FutureTask 的 get() 方法会一直阻塞,一直会等待任务执行完毕,才返回。
System.out.println("value:"+ft.get());
}
线程池执行任务,返回的结果使用 Future 接收。
public class CycleWait4 implements Callable<String>{
private String value;
@Override
public String call() throws Exception {
try {
Thread.currentThread().sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
value = "we have data now";
return value;
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService service = Executors.newCachedThreadPool();
CycleWait4 cycleWait = new CycleWait4();
//将任务提交给线程池处理
Future<String> future = service.submit(cycleWait);
while (!future.isDone()){
Thread.sleep(1000);
}
System.out.println("value:"+future.get());
//关闭线程池
service.shutdown();
}
}
创建后尚未启动。
可能正在运行,也可能正在等待 CPU 时间片。
包含了操作系统线程状态中的 Running 和 Ready。
调用 start()
方法后开始运行,线程这时候处于 Ready 状态。可运行状态的线程获得了 CPU 时间片后就进入Running 状态。
等待获取一个排它锁,如果其他线程释放了锁就会结束此状态。
等待其它线程显式地唤醒,否则不会被分配 CPU 时间片。
进入方法 | 退出方法 |
---|---|
没有设置 Timeout 参数的 Object.wait() 方法 | Object.notify() / Object.notifyAll() |
没有设置 Timeout 参数的 Thread.join() 方法 | 被调用的线程执行完毕 |
LockSupport.park() 方法 | LockSupport.unpark(Thread) |
无需等待其它线程显式地唤醒,在一定时间之后会被系统自动唤醒。
调用 Thread.sleep() 方法使线程进入限期等待状态时,常常用“使一个线程睡眠”进行描述。
调用 Object.wait() 方法使线程进入限期等待或者无限期等待时,常常用“挂起一个线程”进行描述。
睡眠和挂起是用来描述行为,而阻塞和等待用来描述状态。
进入方法 | 退出方法 |
---|---|
Thread.sleep() 方法 | 时间结束 |
设置了 Timeout 参数的 Object.wait() 方法 | 时间结束 / Object.notify() / Object.notifyAll() |
设置了 Timeout 参数的 Thread.join() 方法 | 时间结束 / 被调用的线程执行完毕 |
LockSupport.parkNanos() 方法 | LockSupport.unpark(Thread) |
LockSupport.parkUntil() 方法 | LockSupport.unpark(Thread) |
阻塞和等待的区别在于:
阻塞是被动的,它是在等待获取一个排它锁。
等待是主动的,通过调用 Thread.sleep() 和 Object.wait() 等方法进入。
可以是线程完成任务后自己结束,或者产生了异常而结束。
Executor 管理多个异步任务的执行,而无需程序员显式地管理线程的生命周期。这里的异步是指多个任务的执行互不干扰,不需要进行同步操作。
主要有三种 Executor:
- CachedThreadPool:一个任务创建一个线程;
- FixedThreadPool:所有任务只能使用固定大小的线程;
- SingleThreadExecutor:相当于大小为 1 的 FixedThreadPool。
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
executorService.execute(new MyRunnable());
}
executorService.shutdown();
}
Java 中有两类线程:
- 用户线程(User Thread):运行在前台的线程,比如主线程。
- 守护线程(Daemon Thread):程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。
一旦所有的用户线程都退出了,虚拟机也就退出了,因此不要在守护线程中执行业务逻辑操作,因为随时都可能中断(甚至无法执行 finnally 中的语句)。
注意:在线程启动之前使用 setDaemon() 方法可以将一个线程设置为守护线程。
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.setDaemon(true);
}
-
start()
启动一个新线程,新线程会执行 run() 方法;
start 不能被重复调用,如果重复调用,会抛
java.lang.IllegalException
。 -
run()
和一个成员方法相同,可以被重复调用;
单独调用 run() 不会启动新线程
sleep() 和 wait() 的区别:
-
持有者
sleep() 是 Thread 的静态方法;
wait() 是 Object 的成员方法,所以任何对象都可以调用 wait()
-
锁是否会释放
sleep() 会一直持有同步锁,继续占用 CPU 资源;
wait() 会释放同步锁,使得其他线程可以使用同步块或者同步方法
-
使用范围
wait() 只能在同步代码块或同步方法中使用,否则,会抛
java.lang.IllegalMonitorStateException
;sleep() 可在任何地方使用
Thread.yield() 声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行。该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其它线程可以运行。
yield() 和 wait() 的区别:
-
yield() 是 Thread 的静态方法
wait() 是 Object 的成员方法
-
yield() 让当前线程由“运行状态”进入“就绪状态”;
wait() 让当前线程由“运行状态”进入“等待状态”
-
yield() 不会释放同步锁;
wait() 会释放同步锁,使得其他线程可以使用同步块或者同步方法
锁池:线程 A 已经拥有某个对象的锁,线程 B、C 想要调用这个对象的同步方法,此时线程 B、C 就会被阻塞,进入一个地方去等待锁的释放,这个地方就是该对象的锁池。
等待池:假设线程 A 调用了某个对象的 wait() 方法,线程 A 释放该对象的锁,同时线程 A 进入等待池,进入等待池的线程不会竞争该对象的锁。
notifyAll() 让所有处于等待池的线程进入锁池去竞争锁。
使用退出标志,使线程正常退出,也就是 run 方法完成后线程终止。
需要 while() 循环以某种特定的条件下退出,最直接的方法就是设计一个 boolean 类型的标志,并且通过设置这个标志来控制循环是否退出。 一般需要加上 volatile 来保证标志的可见性。
Thread.stop() 强制终止线程(不建议使用)。
-
线程处于阻塞状态,使用 interrupt() 则会抛出 InteruptedException 异常。
(线程处于阻塞状态)
-
使用 while(! isInterrupted()) {...} 来判断线程是否被中断,使用 interrupt() 则线程终止。
(线程处于运行状态)
综合线程处于“阻塞状态”和“运行状态”的终止方式,通用的终止线程的形式如下:
try{
while(!isInteruppted()){ //isInteruppted() 保证只要中断标记为 true,就终止线程
//...
}
}catch(InteruptedException e){
//保证当发生 InteruptedException 时,线程被终止。
}
举例如下:
public class InterruptExample implements Runnable{
@Override
public void run() {
int i = 0;
try {
//isInteruppted() 保证只要中断标记为 true,就终止线程
while (!Thread.currentThread().isInterrupted()) {
Thread.sleep(100);
i++;
System.out.println(Thread.currentThread().getName() + " (" +
Thread.currentThread().getState() + ") loop " + i);
}
} catch (InterruptedException e) {
// 保证当发生 InteruptedException 时,线程被终止。
System.out.println(Thread.currentThread().getName() + " (" +
Thread.currentThread().getState() + ") catch InterruptedException.");
}
}
public static void main(String[] args) throws InterruptedException {
Runnable interruptTask = new InterruptExample();
Thread t1 = new Thread(interruptTask, "t1");
System.out.println(t1.getName() +" ("+t1.getState()+") is new.");
t1.start(); // 启动“线程t1”
System.out.println(t1.getName() +" ("+t1.getState()+") is started.");
// 主线程休眠300ms,然后主线程给t1发“中断”指令。
Thread.sleep(300);
t1.interrupt();
System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted.");
// 主线程休眠300ms,然后查看t1的状态。
Thread.sleep(300);
System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now.");
}
}
t1 (NEW) is new.
t1 (RUNNABLE) is started.
t1 (RUNNABLE) loop 1
t1 (RUNNABLE) loop 2
t1 (TIMED_WAITING) is interrupted.
t1 (RUNNABLE) catch InterruptedException.
t1 (TERMINATED) is interrupted now.