Java多线程_创建线程的多种方式
Java多线程_创建线程的多种方式
一、线程与进程
几乎所有的操作系统都支持进程的概念,所有运行中的任务都对应一个进程(Process)。当一个程序进入内存运行时,即变成一个进程。进程是处于运行中的程序,并且具有一定的独立功能,进程是系统进行资源调度的一个独立单位。
一般而言,进程包含三个特征:独立性、动态性、并发性。
一个程序运行后至少会有一个进程,一个进程可以包含多个线程,但至少要有个线程。
归纳起来就是说:操作系统可以同时执行多个任务,每个任务就是进程;进程可以同时执行多个任务,每个任务就是线程。
温馨提示(并发性与并行性):
并发性(concurrency)和并行性(parallel)是两个概念,并行是指在同一时刻,有多条指令在多个处理器上同时执行;并发是指在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个进程被同时执行的效果。
二、多线程的优势
线程在程序中是独立的、并发的执行流,与分隔的进程相比,线程分隔程度要小很多。当操作系统创建一个进程时,必须为该进程分配独立的内存空间,并分配大量的相关资源;但创建线程则简单很多,因此使用多线程来实现并发比使用多进程实现并发要强很多。
1、进程之间不能共享内存,但线程之间共享内存非常容易,
2、系统创建进程时需要为该进程重新分配系统资源,但创建线程则代价要小很多,因此使用多线程实现多任务并发要比进程效率高很多。
3、Java语言内置了多线程功能支持,而不是单纯的作为底层操作系统的调度模式,从而简化了Java的多线程编程。
三、线程的创建和启动
Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例,以下则介绍其中三种常见创建线程对象的方法。
1、继承Thread类创建线程类
创建步骤:
①定义Thread类的子类,并重写该类的run()方法,该run()方法体就代表了线程需要完成的任务。因此吧run()方法称为线程执行体。
②创建Thread子类的实例,即创建了线程对象。
③调用线程对象的start()方法来启动该线程。
/**
* @program: code
* @description: 通过继承Thread类创建线程对象
* @author: yangzhihong
* @create: 2022-02-28 20:29
**/
public class Thread_jc extends Thread {
private int i;
//重写run()方法,run()方法的方法体就是线程执行体。
public void run(){
for (i = 0; i < 100; i++) {
//d当线程类继承Thread类时,直接使用this即可获取当前线程
//Thread对象的getName()返回当前线程的名字
//因此可以直接调用getName()方法返回当前线程的名字
System.out.println("我是子线程" + getName() + "" + i);
}
}
public static void main(String[] args){
for (int i = 0; i < 100; i++) {
//调用Thread的currentThread()方法获取当前线程
System.out.println("我是主线程" + Thread.currentThread().getName() + "" + i);
if (i == 20) {
//创建并启动第一个线程
new Thread_jc().start();
//创建并启动第二个线程
new Thread_jc().start();
}
}
}
}
Thread类提供了常用的API
Thread.currentThread() : 静态方法用于获取当前的线程对象。
getName() : 可以获取线程对象的名称。
主线程的名称默认是main,不能修改。
子线程的名称默认是 Thread-0,.......
可以修改,修改方式,可以提供有参构造方法,调用父类的有参构造方法Thread(String name)
也可以使用setName(String name) 设置线程名称。
2、实现Runnable接口创建线程
自定义一个类实现Runnable接口,这个类是一个任务类,这个任务类需要重写Runnable接口中的任务方法,这个类的对象是一个任务对象,如果这个任务对象的任务需要在子线程中执行,必须依赖线程对象。
创建步骤:
①定义Runnable接口的实现类,并重写该方法的run()方法,该run()方法的方法体同样是该线程的线程执行体。
②创建Runnable实现类的实例,并以此作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
③调用线程线程对象的start()方法来启动线程。
**注意:Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。而实际的线程对象依然是Thread实例,只是Thread线程负责执行其target的run()方法。4
/**
* @program: code
* @description: 实现Runnable接口创建线程对象
* @author: yangzhihong
* @create: 2022-03-01 10:24
**/
// 自定义一个任务类
class MyTask implements Runnable {
@Override
public void run() {
for(int i=1; i<=100; i++) {
System.out.println(Thread.currentThread().getName() + "----" + "刷出第" + i + "个小怪");
}
}
}
public class RunnableThread {
public static void main(String[] args) {
// 创建任务对象
MyTask myTask = new MyTask();
// 创建线程对象
Thread thread = new Thread(myTask, "子线程");
// 开启子线程对象的任务
thread.start();
for(int j=1; j<=100; j++) {
System.out.println(Thread.currentThread().getName() + "----" + "刷出第" + j + "个大BOSS");
}
}
}
3、使用Callable和Future创建线程
从Java5 开始,Java提供了Callable接口,该接口与Runnable接口非常相似,Callable接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更强大:可以有返回值、可以声明抛出异常。
Java5 提供了一个与Callable配套的Future接口,Future有个实现类FutureTask,该类的RunnableFuture接口对Future、Runnable都进行了实现。可以作为中介来关联Runnable与Future,并通过Future的公共方法V get() 获取到Callable的返回值,于是乎Callable对象就可以作为Thread的target来创建线程了。(V get()方法中的V是表示泛型,表示Callable接口里的类型必须与call()返回值类型相同)
创建步骤:
①创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且该call()方法有返回值,再创建Callable实现类的实例对象。从Java8 开始可以直接使用Lambda表达式创建Callable对象。
②使用FutureTask类包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
③使用FutureTask对象作为Thread对象的target创建并启动新线程。
④调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @program: code
* @description: 通过Callable与Future来创建线程对象
* @author: yangzhihong
* @create: 2022-03-01 11:56
**/
// 实现了Callable接口的类,也是一个任务类
class MyCall implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i=1; i<=100; i++) {
sum += i;
}
return sum;
}
}
public class CallableFutureThread {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建任务对象
MyCall myCall = new MyCall();
// 创建FutureTask对象这个对象实现了RunnableFuture接口
// RunnableFuture多继承了Runnable接口和Future接口
// 在构造FutureTask对象时,可以通过构造方法将Callable接口的
// 实现对象传入
FutureTask<Integer> futureTask = new FutureTask<>(myCall);
// 创建一个线程对象
Thread thread = new Thread(futureTask);
thread.start();
// 通过get()方法来获取Callable任务方法的返回结果
if(futureTask.isDone()) {
Integer result = futureTask.get();
System.out.println(result);
}
}
}
4、这三种创建线程方式的对比
通过继承Thread类和实现Runnable、Callable接口都可以实现多线程,不过实现Runnable接口与实现Callable接口的方式基本相同,只是Callable接口里定义的方法有返回值和可以声明抛出异常,因此可以将Runnable和Callable归为同一种实现接口方式。
实现接口方式创建线程优缺点:
①线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
②在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源,从而可以将CPU、代码和数据分开。形成清晰的模型。较好的体现了面向对象的思想。
③缺点,编程稍微复杂一些,如果需要访问当前线程,则必须使用Thread.currentThread()方法。
采用继承Thread类方式创建线程优缺点:
①缺点,因为线程类已经继承了Thread类,所以不能再继承其他父类。
②优点,编写简单,如果需要访问当前线程,则不需要使用Thread.currentThread()方法,直接this即可获得当前线程。
因此一般推荐采用实现Runnable接口、Callable接口方式来创建多线程。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)