1.多线程基础
1.1 线程和进程的区别:
进程 | 线程 | |
---|---|---|
定义 | 进程是程序执行时的一个实例,即它是程序已经执行到何种程度的数据结构的汇集。 | 线程是进程的一个执行流,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。 |
根本区别 | 进程是操作系统资源分配的基本单位 | 线程是处理器任务调度和执行的基本单位 |
资源开销 | 每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销 | 可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计 数器(PC),线程之间切换的开销小。 |
包含关系 | 如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的 | 线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。 |
内存分配 | 进程之间的地址空间和资源是相互独立的 | 同一进程的线程共享本进程的地址空间和资源 |
影响关系 | 一个进程崩溃后,在保护模式下不会对其他进程产生影响 | 一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。 |
执行过程 | 每个独立的进程有程序运行的入口. 顺序执行序列和程序出口,可并发执行 | 线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,可并发执行 |
1.2 创建线程的三种方式对比
继承
Thread
类定义Thread类的子类,并重写该类的run方法。
创建Thread子类的实例,即创建了线程对象。
调用线程对象的start()方法来启动线程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27package Thread;
/*
1.继承Thread类
2.重写run()方法
3.调用start()方法开启线程
*/
public class TestThread extends Thread {
public void run() {
//重写run()方法
for (int i = 0; i < 10; i++) {
System.out.println("小明在厨房做饭");
}
}
public static void main(String[] args) {
//创建Thread子类的实例
TestThread testThread = new TestThread();
//调用start()方法开启线程
testThread.start();
for (int i = 0; i < 200; i++) {
System.out.println("小红在客厅打王者");
}
}
}
当多线程没有开启时,代码只执行主线程里面main()方法的代码,当开启线程后,经过cpu调度后开始执行(线程开启后不一定立即执行)
实现
Runnable
接口- 实现Runnable接口,并重写该接口的run()方法。
- 创建Runnable实现类的实例,并用该实例作为Thread的target来创建Thread对象,Thread对象才是真正的线程对象。
- 调用线程对象的start()方法来启动线程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31package Thread;
/*
1.实现Runnable接口
2.重写run()方法
3.创建Runnable接口实现类实例,并将该实例放入Thread的target来创建Thread对象
4.调用start()方法开启线程
*/
public class TestRunnable implements Runnable {
public void run() {
//重写run()方法
for (int i = 0; i < 10; i++) {
System.out.println("小明在厨房做饭");
}
}
public static void main(String[] args) {
//创建Runnable接口实现类实例
TestRunnable testRunnable = new TestRunnable();
//该实例放入Thread的target来创建Thread对象
//调用start()方法开启线程
// Thread thread = new Thread(testRunnable);
// thread.start();
new Thread(testRunnable).start();
for (int i = 0; i < 200; i++) {
System.out.println("小红在客厅打王者");
}
}
}实现
Callable
接口- 实现Callable接口并指定返回值,然后实现call()方法。
- 创建Callable实现类的实例,使用FutureTask包装类来包装Callable对象。
- 使用FutureTask对象作为Thread对象的target创建并启动新线程。
- 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40package Thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/*
1.实现Callable接口。
2.重写call()方法。
3.创建Callable接口实现类实例,使用FutureTask包装类来包装Callable实例对象。
4.使用FutureTask对象作为Thread的target来创建Thread对象。
5.调用start()方法开启线程。
6.调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
*/
public class TestCallable implements Callable<Boolean> {
public Boolean call() throws Exception {
//重写call()方法
for (int i = 0; i < 10; i++) {
System.out.println("小明在厨房做饭");
}
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建Callable接口实现类实例
TestCallable testCallable = new TestCallable();
//使用FutureTask包装类来包装Callable实例对象
FutureTask<Boolean> task = new FutureTask<>(testCallable);
//使用FutureTask对象作为Thread的target来创建Thread对象
//调用start()方法开启线程
new Thread(task).start();
for (int i = 0; i < 200; i++) {
System.out.println("小红在客厅打王者");
}
//调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
System.out.println(task.get()); //获取call()的返回值。若调用时call()方法未返回,则会阻塞线程等待返回值
}
}采用实现
Runnable
、Callable
接口的方式创建多线程优势:
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。劣势:
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
使用继承
Thread类
的方式创建多线程优势:
编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
劣势
线程类已经继承了Thread类,所以不能再继承其他父类。
Runnable
和Callable
的区别- Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()。
- Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
- Call方法可以抛出异常,run方法不可以。
- 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
1.3 为什么要使用多线程
从计算机底层来说:
- 单核时代: 在单核时代多线程主要是为了提高 CPU 和 IO 设备的综合利用率。举个例子:当只有一个线程的时候会导致 CPU 计算时,IO 设备空闲;进行 IO 操作时,CPU 空闲。我们可以简单地说这两者的利用率目前都是 50%左右。但是当有两个线程的时候就不一样了,当一个线程执行 CPU 计算时,另外一个线程可以进行 IO 操作,这样两个的利用率就可以在理想情况下达到 100%了。
- 多核时代:多核时代多线程主要是为了提高 CPU 利用率。举个例子:假如我们要计算一个复杂的任务,我们只用一个线程的话,CPU 只会一个 CPU 核心被利用到,而创建多个线程就可以让多个CPU 核心被利用到,这样就提高了 CPU 的利用率。