第一种:继承Thread类
主要步骤:
1.让子类继承Thread线程类
2.重写里面的run方法(run方法中写要执行的代码)
3.创建一个Thread对象,代表一个线程
4.启动线程
每一块代码中都含有详细的注释,一定认真看完
代码实现:
public class MyThread extends Thread{
//1.让子类继承Thread线程类
//2.重写里面的run方法
@Override
public void run() {
//描述线程的执行任务
for (int i = 0; i < 5; i++) {
System.out.println("子线程MyThread线程输出" + i);
}
}
}
public class ThreadTest1 {
//掌握线程的创建方式一:继承Thread类
public static void main(String[] args) {
//main方法是由一条默认的主线程负责执行
//3.创建一个Thread对象,代表一个线程
Thread t = new MyThread();
//4.启动线程
t.start();//main线程 t线程
for (int i = 0; i < 5; i++) {
System.out.println("主线程main输出" + i);
}
}
}
多线程的注意事项:
1.启动线程必须调用start方法,不是调用run方法,若调用run相当于普通方法的调用 ,而调start方法则向向CPU注册线程
2.不要把主线程的任务放在子线程之前,不然永远是主线程的先跑完,之后才是子线程
为什么启动一个线程不直接调用run(),而要调用start()启动?
1.如果直接调用run()方法,那么它就只是作为一个普通方法的调用,程序中依然只有一个主线程(main),并且只能顺序执行,需要等待run()方法执行结束后才能继续执行后面的代码。
2.创建线程的目的是为了更充分地利用CPU里面的资源,如果直接调用run()方法,就失去了创建线程的意义。
此方法创建多线程的优缺点
1.优点:编码简单
2.缺点:线程类已经继承了Thread类,无法继承其他类,不利于功能的扩展
第二种:实现Runnable接口
主要步骤:
1.定义一个任务类,实现Runnable接口
2.重写runnable中的run方法
3.创建任务对象
4.把任务对象交个一个线程对象处理
代码实现:
//1.定义一个任务类,实现Runnable接口
public class MyRunnable implements Runnable{
//2.重写runnable中的run方法
@Override
public void run() {
//线程要执行的任务
for (int i = 0; i < 5; i++) {
System.out.println("子线程输出" + i);
}
}
}
public class ThreadTest2 {
//掌握多线程的创建方式二:实现Runnable接口
public static void main(String[] args) {
//3.创建任务对象
Runnable target = new MyRunnable();
//任务对象没有start()方法
//4.把任务对象交个一个线程对象处理
// Thread 提供有参构造器: public Thread(Runnable target)
new Thread(target).start();
for (int i = 0; i < 5; i++) {
System.out.println("主线程main输出" + i);
}
}
}
此方法的优缺点:
1.任务类只是实现接口,可以继续继承其他类,实现其它接口,扩展性强
2.缺点:多创建一个runnalbe对象(可以忽略,相当于没有缺点)
第二种方法的匿名内部类写法
这里直接看代码:对于Lambda的使用,可以去看这里:
https://blog.youkuaiyun.com/liusaidh/article/details/135313919?spm=1001.2014.3001.5501
public class ThreadTest2_2 {
//目标:掌握多线程创建方式二的匿名内部类的写法
public static void main(String[] args) {
//1.直接创建一个Runnable接口的匿名内部类形式(任务对象)
Runnable target = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程1输出" + i);
}
}
};
new Thread(target).start();
//简化形式1:
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程2输出" + i);
}
}
}).start();
//因为Runnable接口为函数式接口,所以可以用Lambda表达式简化
//简化形式2:
new Thread(()-> {
for (int i = 0; i < 5; i++) {
System.out.println("子线程3输出" + i);
}
}).start();
for (int i = 0; i < 5; i++) {
System.out.println("主线程main输出" + i);
}
}
}
第三种:用Callable接口和FutureTask类来实现
前两种线程创建方式都存在一个问题:
假如线程执行完毕后,有一些数据要返回,而他们重写的run方法均不能直接返回结果
怎么解决这个问题?那就要用到第三种方法了
主要步骤:
1.让这个类实现Callable接口
2.重写call方法
3.创建一个Callable对象
4.把Callable对象封装成一个FutureTask对象(任务对象)
5.把任务对象交给一个Thread对象//6.获取线程执行完毕后的结果
代码:
import java.util.concurrent.Callable;
//1.让这个类实现Callable接口
public class MyCallable implements Callable<String> {//填写要返回的类型
private int n;
public MyCallable(int n) {
this.n = n;
}
//2.重写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;
}
}
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadTest3 {
public static void main(String[] args) throws Exception {
//3.创建一个Callable对象
Callable<String> call = new MyCallable(100);
//4.把Callable对象封装成一个FutureTask对象(任务对象)
//未来任务类对象作用:
//1.是一个任务类对象,实现了Runnable接口
//2.可以在线程执行完毕后,用未来任务类对象调用get方法获取线程执行完毕后的结果
FutureTask<String> f1 = new FutureTask<>(call);
//5.把任务对象交给一个Thread对象
new Thread(f1).start();
//6.获取线程执行完毕后的结果
//注意:如果执行到这里,假如上面的代码还没有执行完毕
//这里的代码会暂停,等上面的代码执行完毕后才会获取结果
String s = f1.get();
System.out.println(s);
}
}
此方法的优缺点:
1.优点:线程任务类只是实现接口,可以继续继承和实现接口,扩展性强;可以在线程执行完毕后去获取线程的执行结果
2.缺点:编码复杂一点