什么是线程?
建议把主线程任务都放在子线程之后,不要把主线程任务放在子线程之前!
- 实现多线程的方式都在这个Thread类里面,因为Thread类代表线程!
Thread类:线程类
- Java是通过java.lang.Thread类来代表线程的。
- 按照面向对象的思想,Thread类应该提供了实现多线程的方式。
多线程的实现方式一:继承Thread类
- 定义一个子类MyThread继承线程类Thread,重写run()方法
- 创建MyThread类的对象
- 调用线程对象的start()方法启动线程(启动线程后会自动执行run方法)
- 当run方法执行完,该线程结束,资源被回收(一旦结束不能重新启动)
- run()方法属于线程的任务方法
- 建议把子线程任务放在主线程之前!
继承Thread类实现多线程的优缺点:
- 优点:编码简单
- 缺点:存在单继承的局限性,线程类已经继承Thread类,无法 / 不能继承其他类,不利 / 便于扩展。
为什么不直接调用run方法,而是调用start方法启动线程?
- 直接调用run方法会当成普通方法来执行,此时相当于还是单线程执行。
- 只有调用start方法才是启动一个新的线程执行。
不要把主线程任务放在子线程之前,建议把主线程任务都放在子线程之后,为什么?
- 如果把主线程任务放在子线程之前,这样主线程一直是先跑完的,相当于还是一个单线程的效果了。
package com.gch.d1_create;
/**
目标:多线程的创建方式一:继承Thread类实现。
*/
public class ThreadDemo1 {
public static void main(String[] args) {
// 3.new一个新线程对象
Thread t = new MyThread(); // 多态
// 4.调用start方法启动线程(执行的还是run方法)
t.start(); // start方法告诉操作系统这边开启了一个新线程,通知cpu以线程的方式来启动run方法
// t.run(); // 它会认为这是个普通类,普通类调用普通方法,程序由上往下执行
for (int i = 0; i < 5; i++) {
System.out.println("主线程执行输出:" + i);
}
}
}
/**
1.定义一个线程类继承Thread类
*/
class MyThread extends Thread{
/**
2.重写run方法,里面是定义线程以后要干啥
*/
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程执行输出:" + i);
}
}
}
多线程的实现方案二:实现Runnable接口
- 定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
- 创建MyRunnable线程任务对象
- 把MyRunnable任务对象交给Thread线程对象处理
- 调用线程对象的start()方法启动线程
package com.gch.d1_create;
/**
目标:学会线程的创建方式二,理解它的优缺点
*/
public class ThreadDemo2 {
public static void main(String[] args) {
// 3.创建一个任务对象
Runnable target = new MyRunnable();
// 4.把任务对象交给Thread处理
Thread t = new Thread(target);
// Thread t = new Thread(target,"线程名");
// 5.启动线程
t.start(); // 开始出现两个线程
// 两个线程同时执行,同时执行的话是有先后的
for (int i = 0; i < 10; i++) {
System.out.println("主线程执行输出:" + i);
}
}
}
/**
* 1.定义一个线程任务类 实现Runnable接口
*/
class MyRunnable implements Runnable {
/**
2.重写run方法,定义线程的执行任务
*/
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("子线程执行输出:" + i);
}
}
}
实现Runnable接口来实现多线程的优缺点:
- 优点:线程任务类只是实现Runnable接口,它可以继续继承类和实现其他接口,扩展性强。
- 缺点:编程多一层对象包装(把线程任务对象交给线程对象),如果线程有执行结果是不可以直接返回的(因为run方法返回值为void空)
创建Runnable的匿名内部类对象来实现多线程:
- 创建Runnable的匿名内部类对象
- 交给Thread线程对象处理
- 调用线程对象的start()方法启动线程
package com.gch.d1_create;
/**
目标:学会线程的创建方式二(匿名内部类方式实现,语法形式)
*/
public class ThreadDemo2Other {
public static void main(String[] args) {
// 1.创建Runnable的匿名内部类对象
Runnable target = new Runnable() {
// 2.重写run方法,定义线程的执行任务
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("子线程1执行输出:" + i);
}
}
};
// 3.交给Thread处理
Thread t = new Thread(target);
// 4.调用线程对象的start方法启动线程
t.start();
// 1.还可以继续简化,把Runnable的匿名内部类对象作为Thread线程对象的入参
Thread t2 = new Thread(new Runnable() {
// 2.重写run方法,定义线程的执行任务
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("子线程2输出:" + i);
}
}
});
// 3.调用线程对象的start方法启动线程
t2.start();
// 或者直接newThread类的对象然后直接调用start方法
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("子线程3输出:" + i);
}
}
}).start();
// Lambda表达式进一步简化
new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("子线程输4出:" + i);
}
}).start();
/**
主线程
*/
for (int i = 0; i < 10; i++) {
System.out.println("主线程执行输出:" + i);
}
}
}
1. 前两种线程创建方式都存在一个问题:
- 它们重写run方法均不能直接返回结果
- 继承Thread类、实现Runnable接口不适合需要返回线程执行结果的业务场景。
2. 怎么解决这个问题?
- JDK 5.0提供了Callable接口和FutureTask(未来任务对象)来实现。
- 这种方式的优点是:可以得到线程执行的结果。
多线程的实现方式三:利用Callable接口、FutureTask(未来任务对象)实现
- 得到任务对象
- 定义类实现Callable接口,重写call方法,封装要做的事情
- 用FutureTask把Callable对象封装成线程任务对象
- 把线程任务对象交给Thread线程对象处理
- 调用Thread线程对象的start方法启动线程,执行任务
- 线程执行完毕后,通过FutureTask的get()方法去获取任务执行的结果。
package com.gch.d1_create;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
目标:学会线程的创建方式三:实现Callable接口,结合FutureTask完成。
*/
public class ThreadDemo3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 3.创建Callable任务对象
Callable call = new MyCallable(100);
// 4.把Callable任务对象交给FutureTask对象作为线程任务对象
// FutureTask:未来任务对象
// FutureTask对象的作用1:是Runnable对象(实现了Runnable接口),可以交给Thread作为线程任务对象了
// FutureTask对象的作用2:可以在线程执行完毕之后通过调用其get方法得到线程执行完成的结果
FutureTask ft = new FutureTask<>(call);
// 5.交给线程处理
Thread t = new Thread(ft);
// 6.启动线程
t.start();
// 如果call方法没有执行完毕,这里的代码会等待,直到线程跑完才提取结果。
String rs = ft.get();
System.out.println(rs); // 子线程执行的结果是:5050
}
}
/**
* 1.定义一个任务类,实现Callable接口 应该申明线程任务执行完毕后的结果的数据类型
*/
class MyCallable implements Callable {
private int n;
public MyCallable(int n){
this.n = n;
}
/**
2.重写call方法(任务方法)
*/
@Override
public String call() throws Exception {
int sum = 0;
for(int i = 1;i <= n;i++){
sum += i;
}
return "子线程执行的结果是:" + sum;
}
}
利用实现Callable接口和FutureTask未来任务对象来实现多线程的优缺点:
- 优点:任务类只是实现接口,可以继续继承类和实现其他接口,扩展性强。而且可以在线程执行完毕后去获取线程执行的结果。
- 缺点:编码复杂一点。
Thread常用API说明
package com.gch.d2_api;
public class MyThread extends Thread {
/**
* 调用父类的有参构造器
* @param name:线程名
*/
public MyThread(String name){
// 为当前线程对象设置名称,送给父类的有参数构造器初始化名称
super(name);
}
public MyThread() {
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "输出:" + i);
}
}
}
package com.gch.d2_api;
/**
目标:线程的API
*/
public class ThreadDemo01 {
// main方法是由主线程负责调度的
public static void main(String[] args) {
Thread t1 = new MyThread("Thread-0");
// t1.setName("线程1");
t1.start();
System.out.println(t1.getName());
Thread t2 = new MyThread("Thread-1");
// t2.setName("线程2");
t2.start();
System.out.println(t2.getName());
// 哪个线程执行它,它就得到哪个线程对象(当前线程对象)
// 主线程的默认名称就叫main
Thread m = Thread.currentThread();
// m.setName("最牛的线程");
System.out.println(m.getName()); // main
for (int i = 0; i < 5; i++) {
System.out.println(m.getName()+ "输出:" + i);
}
}
}
package com.gch.d2_api;
/**
目标:线程的API
*/
public class ThreadDemo02 {
// main方法是由主线程负责调度的
public static void main(String[] args) throws Exception {
for (int i = 1; i <= 5; i++) {
System.out.println("主线程输出:" + i);
if(i == 3){
// 让当前线程进入休眠状态 单位:毫秒
// 段子:项目经理让我加上这行代码,如果用户愿意交钱,我就注释掉
Thread.sleep(3000);
}
}
}
}