欢迎来到“雪碧聊技术”CSDN博客!
在这里,您将踏入一个专注于Java开发技术的知识殿堂。无论您是Java编程的初学者,还是具有一定经验的开发者,相信我的博客都能为您提供宝贵的学习资源和实用技巧。作为您的技术向导,我将不断探索Java的深邃世界,分享最新的技术动态、实战经验以及项目心得。
让我们一同在Java的广阔天地中遨游,携手提升技术能力,共创美好未来!感谢您的关注与支持,期待在“雪碧聊技术”与您共同成长!
目录
①定义一个线程任务类,实现Runnable接口,并重写run方法
③把任务对象交给Thread类的构造器,将其封装为一个线程对象
一、什么是多线程?
1、什么是线程?
线程(Thread)是一个程序内部的一条执行流程。
举例:
2、什么是多线程?
多线程:指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行)。
3、多线程用在哪里?有什么用?
举例1:12306购票
网上买票的时候,肯定不止你自己,而是成千上万的人同时在购票,此时就需要12306的服务器提供很多的执行流程(即,很多线程)来进行购票业务的执行。
举例2:百度网盘上传、下载同时完成
一个线程负责下载,一个线程负责上传。
二、线程的创建方式1-继承Thread类
1、步骤
①创建一个类,继承Thread类,代表该类是一个线程类
②重写Thread类的run方法
③创建线程类的一个对象,代表一个新线程
④调用该对象的start方法,启动该线程
2、举例
public class test1 {
public static void main(String[] args) {
//目标:掌握线程的创建方式一:继承Thread类
//4、创建线程类的对象,一个对象就是一个线程
MyThread t1 = new MyThread();
//5、调用对象的start方法,启动线程
t1.start();
//主线程(main函数所在的那条执行流程)代码
for (int i = 0; i < 5; i++) {
System.out.println("主线程输出:"+i);
}
}
}
//1、定义一个子类继承Thread类,成为一个线程类
class MyThread extends Thread{
//2、重写Thread类的run方法
public void run() {
//3、在run方法中编写线程的任务代码(线程要干的活)
for (int i = 0; i < 5; i++) {
System.out.println("子线程输出:"+i);
}
}
}
运行结果:
主线程输出:0
主线程输出:1
子线程输出:0
子线程输出:1
子线程输出:2
子线程输出:3
子线程输出:4
主线程输出:2
主线程输出:3
主线程输出:4
可见此时主线程和子线程是并发执行的关系,输出的for循环都是交叉的,因此这就是多线程。
3、注意事项
①启动线程必须是调用start方法,而不是调用run方法
简单来说:只有通过start方法,才能启动一个新的线程。而run方法不行。
- 直接调用run方法,会把该方法当成一个普通方法执行,此时相当于单线程。
- 只有调用start方法才是启动一个新的线程执行。
举例:
运行效果:
②不要把主线程任务放在启动子线程之前
- 这样主线程的任务先跑完,再启动子线程,那效果还是单线程的效果。
举例:
运行效果:
4、优缺点
- 优点:编码简单
- 缺点:存在单继承的局限性,线程类继承Thread后,不能再继承其他类,不便于功能扩展。
三、线程的创建方式2-实现Runnable接口
1、步骤
①定义一个线程任务类,实现Runnable接口,并重写run方法
②创建MyRunnable任务对象
③把任务对象交给Thread类的构造器,将其封装为一个线程对象
④调用线程对象的start方法启动线程。
2、举例
public class test2 {
public static void main(String[] args) {
//目标:掌握线程的创建方式2:实现Runnable接口
//2、创建MyRunnable任务对象
MyRunnable r = new MyRunnable();
//3、把任务对象交给Thread类的构造器,将其封装为一个线程对象
Thread t1 = new Thread(r);
//4、调用线程对象的start方法启动线程。
t1.start();
//主线程的任务
for (int i = 0; i < 5; i++) {
System.out.println("主线程输出:"+i);
}
}
}
//1、定义一个线程任务类,实现Runnable接口,并重写run方法
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程输出:"+i);
}
}
}
运行效果:
主线程输出:0
主线程输出:1
子线程输出:0
主线程输出:2
主线程输出:3
子线程输出:1
主线程输出:4
子线程输出:2
子线程输出:3
子线程输出:4
可见此时主线程、子线程交替输出,是并发的。
3、优缺点
- 优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。
- 缺点:需要多一个实现Runnable接口的步骤
4、简化写法:匿名内部类写法
public class test3 {
public static void main(String[] args) {
//掌握:匿名内部类写法
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程输出:"+i);
}
}
}).start();
//主线程任务
for (int i = 0; i < 5; i++) {
System.out.println("主线程输出:"+i);
}
}
}
运行效果:
子线程输出:0
主线程输出:0
主线程输出:1
主线程输出:2
主线程输出:3
子线程输出:1
主线程输出:4
子线程输出:2
子线程输出:3
子线程输出:4
可见此时主线程和子线程还是交替输出的,是并发关系。
代码解释:
因此上述代码就可以理解为:
new Thread(任务类对象).start();
因此此时Thread类的构造器,会将任务类对象封装成一个线程对象,此时在调用该线程对象的.start方法,即可启动该线程。
四、线程的创建方式3-实现Callable接口
1、前两种创建线程方式存在的问题
线程执行完毕后有一些数据需要返回,他们重写的run方法均不能返回结果。
2、怎么解决这个问题?
JDK 5.Callable接口和FutureTast类来实现。
3、步骤
public class Test4 {
public static void main(String[] args) {
//目标:掌握多线程的创建方式三:实现Callable接口。
//方式三的优势是:可以获取线程执行完毕后的结果
//3、创建一个实现Callable接口的实现类对象
Callable<String> c1 = new MyCallable(100);//多态的写法
//4、把Callable对象封装成一个真正的线程任务对象FutureTask对象
/**
* 未来任务对象的作用:
* ①本质是一个Runnable线程任务对象,可以交给Thread线程对象处理
* ②可以获取线程执行完毕后的结果
*/
FutureTask<String> f1 = new FutureTask<>(c1);
//5、把FutureTask对象作为参数传递给Thread线程对象
Thread t1 = new Thread(f1);
//6、启动线程
t1.start();
Callable<String> c2 = new MyCallable(50);//多态的写法
FutureTask<String> f2 = new FutureTask<>(c2);
Thread t2 = new Thread(f2);
t2.start();
//获取线程执行完毕后的结果
try {
//如果主线程发现第一个线程还没有执行完毕,会让出CPU,等第一个线程执行完毕后,才会往下执行!
//这样才能保证拿到的结果是准确的,防止了线程运行一半,就将结果获取。
System.out.println(f1.get());
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
try {
//如果主线程发现第二个线程还没有执行完毕,会让出CPU,等第二个线程执行完毕后,才会往下执行!
//这样才能保证拿到的结果是准确的,防止了线程运行一半,就将结果获取。
System.out.println(f2.get());
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
}
//1、定义一个实现Callable接口的实现类
class MyCallable implements Callable<String>{
private int n;
public MyCallable(int n){
this.n = n;
}
//2、重写call方法,定义线程执行体
public String call() throws Exception {
int sum = 0;
for (int i = 0; i <= n; i++) {
sum+=i;
}
return "子线程1-"+n+"的和是:"+sum;
}
}
4、执行流程
- 线程执行体,是Callable接口的call方法;
- 获取线程运行的结果,靠FutureTask类的get()方法;
- 启动线程,是靠Thread类的start()方法。
5、优缺点
- 优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;可以在线程执行完毕后去获取线程执行的结果。
- 缺点:编码复杂一点。
五、三种创建线程方式的总结
结语
以上就是多线程的三种创建方式,想了解更多的多线程知识,请关注博主的本专栏文章~~
标签:输出,Thread,创建,主线,start,线程,new,多线程 From: https://blog.csdn.net/qq_63981644/article/details/143778160