Java多线程编程(一)——线程的3种实现方法
什么是多线程?
是指从软件或者硬件上实现多个线程并发执行的技术。
具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能。
并发与并行
并发:在同一时刻,有多个指令在单个CPU上交替执行。
并行:在同一时刻,有多个指令在多个CPU上同时执行。
进程与线程
-
进程:是正在运行的程序
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位 动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的 并发性:任何进程都可以同其他进程一起并发执行
-
线程:是进程中的单个顺序控制流,是一条执行路径
单线程:一个进程如果只有一条执行路径,则称为单线程程序
多线程:一个进程如果有多条执行路径,则称为多线程程序
Java多线程3种实现方式
(1)继承Thread类
(2)实现Runnable接口
(3)实现Callable接口
3种方法各有优缺点,实际开发中,Runnable与Callable的方式比较常用。
(1)继承Thread类的方式
方法介绍:
void run() | 在线程开启后,此方法将被调用执行 |
void start() | 使此线程开始执行,Java虚拟机会调用run方法() |
步骤:
-
定义一个类MyThread继承Thread类
-
在MyThread类中重写run()方法
-
创建MyThread类的对象
-
启动线程
Codinig:
这里需要创建两个类:MyThrad 与 demo01
package 多线程.ThreadDemo01;
public class MyThread extends Thread{
@Override
public void run() {
//100.fori
for (int i = 0; i < 100; i++) {
System.out.println("线程执行了" + i);
}
}
}
package 多线程.ThreadDemo01;
public class demo01 {
public static void main(String[] args) {
MyThread th1 = new MyThread();
MyThread th2 = new MyThread();
th1.start();
th1.start();
}
}
注意点:
为什么要重写run()方法???
因为run()是用来封装被线程执行的代码,重写它,当线程被创建、调用后,就会默认执行run()方法直接调用。
run() 与 start() 方法有什么区别???
run():封装线程执行的代码,直接调用,相当于普通方法的调用
start():启动线程;然后由JVM调用此线程的run()方法!
th1.run() 与 th1.start()的区别 ???
th1.run() 表示创建对象,用对象去调用方法,并没有开启线程!!!
而th1.start() 会和操作系统的资源交互,去开启一条线程
(2)实现Runnable接口
步骤:
-
定义一个类MyRunnable实现Runnable接口
-
在MyRunnable类中重写run()方法
-
创建MyRunnable类的对象
-
创建Thread类的对象,把MyRunnable对象作为构造方法的参数
-
启动线程
Coding:
package 多线程.ThreadDemo02;
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("多线程的实现" + i);
}
}
}
package 多线程.ThreadDemo02;
public class demo02 {
public static void main(String[] args) {
//创建一个参数对象
MyRunnable mr = new MyRunnable();
//创建一个线程对象,把参数传递给这个线程
//在线程启动之后,执行的就是参数里面的run()方法
Thread t1 = new Thread(mr);
t1.start();
Thread t2 = new Thread(mr);
t2.start();
}
}
注意:
Runnable接口中没有start()方法,所以不能直接使用mr.start(),要将MyRunnable实例化的类对象作为参数传递给Thread类。
(3)实现Callable接口
Callable
是一个用于定义多线程执行的代码逻辑的接口,里面定义了一个call()
方法,类似于Runnable
中的run()
方法。子类实现了Callable
接口后就可以重写call()
方法的执行逻辑,然后交给系统调度就好了。
步骤:
-
定义一个类MyCallable实现Callable接口
-
在MyCallable类中重写call()方法
-
创建MyCallable类的对象
-
创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
-
创建Thread类的对象,把FutureTask对象作为构造方法的参数
-
启动线程
-
再调用get方法,就可以获取线程结束之后的结果。
Callable接口的关系比较复杂
Callable
这个接口并没有什么特殊的,只是定义了一个call()
,它的特性是依赖于FutureTask
类来实现的,FutureTask
的顶部接口实现了Runnable
和Future
接口。
Callable接口流程
首先,线程的启动一定是通过Thread对象的start()方法,在Thread类的构造方法中,可以传入一个Runnable作为参数,线程启动后,操作系统调度线程回调Runnable中的run()方法,达到异步调用的效果。
在FutureTask类的顶层接口中实现了Runnable接口,线程获取到CPU资源后,就会去回调FutureTask中的run()方法,所以只需要在run()方法中去调用call()就实现了Callable的调度。
Coding:
package 多线程.ThreadDemo03;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println("第三种线程启动方式" + i);
}
//返回值就表示线程运行完毕的结果
//上面的 泛型 就表示返回值的数据类型
//第三种方法最大的区别 (优势)
return "启动";
}
}
package 多线程.ThreadDemo03;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Demo03 {
public static void main(String[] args) throws ExecutionException, InterruptedException{
MyCallable mc = new MyCallable();
FutureTask<String> ft = new FutureTask<>(mc);
Thread t1 = new Thread(ft);
t1.start();
System.out.println(ft.get());
}
}
这一张图,很深刻!!!
三种方法对比
-
实现Runnable、Callable接口
-
好处: 扩展性强,实现该接口的同时还可以继承其他的类
-
缺点: 编程相对复杂,不能直接使用Thread类中的方法
-
-
继承Thread类
-
好处: 编程比较简单,可以直接使用Thread类中的方法
-
缺点: 可以扩展性较差,不能再继承其他的类
-
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)