概述
什么是线程
- 线程(Thread)是一个程序内部的一条执行流程。
- 程序中如果只有一条执行流程,那这个程序就是单线程的程序。
多线程是什么
- 多线程是指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行)。
如何在程序中创建出多线程
- Java是通过java.lang.Thread类的对象来代表线程的。
多线程的创建
多线程的创建方式一:继承Thread类
- 定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法
- 创建MyThread类的对象
- 调用线程对象的start()方法启动线程(启动后还是执行run方法的)
方式一优缺点:
- 优点:编码简单
- 缺点:线程类以及继承Thread类,无法继承其他类,不利于功能的扩展。
ThreadTest1类
public class ThreadTest1 {
// main方法是由一条默认的主线程负责执行
public static void main(String[] args) {
// 创建MyThread线程类的对象代表一个线程
Thread t = new MyThread();
// 启动线程(自动执行run方法的)
t.start();
for (int i = 1; i < 5; i++) {
System.out.println("主线程main输出:" + i);
}
}
}
MyThread类
public class MyThread extends Thread{
// 必须重写Thread类的run方法
@Override
public void run() {
for (int i = 1; i < 5; i++) {
System.out.println("子线程MyThread输出:" + i);
}
}
}
多线程的注意事项
1、启动线程必须是调用start方法,不是调用run方法。
- 直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。
- 只有调用start方法才是启动一个新的线程执行。
2、不要把主线程任务放在启动线程之前。
- 这样主线程一直是先跑完的,相当于是一个单线程的效果了。
线程创建方式二:实现Runnable接口
- 定义一个线程任务类MyRunnable接口,重写run()方法
- 创建MyRunnable任务对象
- 把MyRunnable任务对象交给Thread处理
- 调用线程对象的start()方法启动线程
Thread类提供的构造器 | 说明 |
---|---|
publci Thread(Runnable target) | 封装Runnable对象成为线程对象 |
方式二的优缺点
- 优点:任务类知识实现接口,可以继承其他类、实现其他接口,扩展性强
- 缺点:需要多一个Runnable对象
ThreadTest2类
public class ThreadTest2 {
public static void main(String[] args) {
// 创建任务对象
Runnable target = new MyRunnable();
// 把任务对象交给一个线程对象处理
new Thread(target).start();
for (int i = 1; i <= 5; i++) {
System.out.println("主线程main输出:" + i);
}
}
}
MyRunnable类
public class MyRunnable implements Runnable{
//重写runnable的run方法
@Override
public void run() {
// 线程要执行的任务
for (int i = 1; i <= 5; i++) {
System.out.println("子线程输出:" + i);
}
}
}
线程创建方式二的匿名内部类写法
- 可以创建Runnable的匿名内部类对象
- 再交给Thread线程对象
- 再调用线程对象的start()启动线程
public class ThreadTest2_2 {
public static void main(String[] args) {
Runnable target = new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println("子线程1输出:" + i);
}
}
};
// 简化形式1:
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println("子线程2输出:" + i);
}
}
}).start();
//简化形式2:
new Thread(() -> {
for (int i = 1; i <= 5; i++) {
System.out.println("子线程3输出:" + i);
}
}).start();
new Thread(target).start();
for (int i = 1; i <= 5; i++) {
System.out.println("主线程main输出:" + i);
}
}
}
线程创建方式三:实现Callable接口
前两种线程创建方式搜存在的一个问题
- 假如线程执行完毕后有以下数据需要返回,他们重写的run方法均不能直接返回结果。
怎么解决这个问题?
- JDK5.0提供了Callable接口和FutureTask类来实现(多线程的第三种创建方式)。
- 这种方式最大的优点:可以返回线程执行完毕后的结果。
多线程的第三种创建方式:利用Callable接口、FutureTask类来实现
- 创建任务对象
- 定义一个类实现Callable接口,重写call方法,封装要做的事情,和要返回的数据
- 把Callable类型的对象封装成FutureTask(线程任务对象)
- 把线程任务对象交给Thread对象
- 调用Thread对象的start方法启动线程
- 线程执行完毕后、通过FutureTask对象的get方法去获取线程任务执行的结果
FutureTask的API
FutureTask提供的构造器 | 说明 |
---|---|
public FutureTask<>(Callable call) | 把Callable对象封装成FutureTask对象 |
FutureTask提供的方法 | 说明 |
---|---|
public V get()throw Exception | 获取线程执行call方法返回的结果 |
线程创建方式三的优缺点
- 优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;可以再线程执行完毕后去获取线程执行的结果。
- 缺点:编码复杂一点。
ThreadTest3类
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadTest3 {
public static void main(String[] args) throws Exception {
// 创建一个Callable的对象
Callable<String> call = new MyCallable(100);
// 把Callable的对象封装成一个FutureTask对象(任务对象)
// 未来任务对象的作用?
// 1.是一个任务对象,实现了Runnable对象
// 2.可以再线程执行完毕后,用未来任务对象调用get方法获取线程执行完毕后的结果
FutureTask<String> f1 = new FutureTask<>(call);
// 把任务对象交给一个Thread对象
new Thread(f1).start();
// 获取线程执行完毕后返回的结果
// 注意:如果代码执行到这,假如上面的线程还没有执行完毕,这里的代码会暂停,等上面线程执行完毕后才会获取结果
String rs = f1.get();
System.out.println(rs);
}
}
MyCallable类
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n) {
this.n = n;
}
// 重写call方法
@Override
public String call() throws Exception {
// 描述线程的任务,返回线程执行返回后的结果
// 需求:求1~n的和返回
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
return "线程求出了1~" + n + "的和为:" + sum;
}
}