一、实现多线程的三种方式:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
实现多线程的方式一:继承Thread类
- 继承Thread类可以理解为:把【线程】和【任务】(要执行的代码)合并在了一起
- Thread类两个重要方法:
方法名 | 说明 |
---|---|
void run() | 在线程开启后,此方法将被调用执行 |
void start() | start方法才是启动线程的方法,调用此方法使当前线程开始执行,Java虚拟机会调用run方法() |
-
继承Thread类实现多线程的步骤:
- 自定义线程类的创建:
- 第一步:自定义一个类(MyThread)继承Thread类
- 第二步:在自定义类(MyThread)中重写Thread的run方法
- 自定义线程类的使用:(写一个测试类)
- 第三步(使用自己创建的线程类):创建线程对象
- 第四步:调用start方法开启线程
- 自定义线程类的创建:
-
代码示范:
- 1、自定义Thread类:
public class MyThread extends Thread {
@Override
public void run() {
// run方法里面实现需要的代码逻辑
for(int i=0; i<100; i++) {
System.out.println(i);
}
}
}
- 2、写一个测试类开启线程:
- 注意:
main线程是程序启动时自动创建的第一个线程,我们写的很多代码都是在main线程中执行的
- 注意:
public class MyThreadDemo {
public static void main(String[] args) {
// 这里其实有个main线程
//创建两个线程
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
//void start() 调用此方法使此线程开始执行; Java虚拟机调用此线程的run方法
my1.start();
my2.start();
}
}
问题1: 为什么要重写run()方法?
因为run()是用来封装被线程执行的代码
问题2:run()方法和start()方法的区别?
- run():封装线程执行的代码,直接调用,相当于普通方法的调用
- start():
启动线程
,然后由JVM调用此线程的run()方法
实现多线程的方式二:实现Runnable接口
- 可以理解为把【线程】和【任务】(要执行的代码)分开
- Runnable 可运行的任务(线程要执行的代码)
- Thread 代表线程(把Runnable作为参数传进来)
-
Thread构造方法
方法名 说明 Thread(Runnable target) 分配一个新的Thread对象,传一个runnable对象进去 Thread(Runnable target, String name) 分配一个新的Thread对象,传一个runnable对象以及线程的名称
-
通过实现Runnable接口实现多线程的步骤:
- 自定义线程类的创建:
- 第一步:自定义一个类(MyRunnable)实现Runnable接口
- 第二步:在自定义类(MyRunnable)中重写Runnable接口的run方法
- 自定义线程类的使用:(写一个测试类)
- 第三步:创建自定义线程类对象
- 第四步:创建线程类对象,并把自定义线程类对象作为构造参数
- 第五步:调用start方法启动线程
- 自定义线程类的创建:
-
代码示范:
- 1、自定义线程类
public class MyRunnable implements Runnable {
@Override
public void run() {
// run方法中实现代码逻辑
for(int i=0; i<100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
线程的构造函数的理解——Thread(Runnable target, String name)
-
构造函数public Thread(Runnable target, String name) 的理解:它是Thread类的一个公开构造函数,它接受两个参数:一个Runnable类型的target和一个String类型的name。
- Runnable是一个接口:其中定义了一个无返回值、无参数的run方法。任何实现了Runnable接口的类都必须提供这个run方法的实现。
- target参数的作用:当线程启动时(即调用线程的start方法时):
- target参数不为null:那么线程会调用target对象的run方法。这意味着你可以将任何实现了Runnable接口的对象的实例传递给Thread的构造函数,然后当线程启动时,该Runnable接口的对象的run方法会被执行。
- target为null:如果在创建Thread对象时,target参数被设置为null,那么当线程启动时,线程会调用自己的run方法(即Thread类中的run方法)。默认情况下,Thread类中的run方法不执行任何操作(它是一个空实现)。因此,如果你希望线程执行特定的任务,你通常会创建一个实现了Runnable接口的类,然后将其实例传递给Thread的构造函数。
- name参数的作用:name参数用于为线程设置一个名称。这有助于在调试和日志记录时识别线程。线程的名称可以通过调用线程的getName方法来获取。
-
2、编写测试类
public class MyRunnableDemo {
public static void main(String[] args) {
//创建MyRunnable类的对象
MyRunnable my = new MyRunnable();
//创建Thread类的对象,把MyRunnable对象作为构造方法的参数
//Thread(Runnable target)
// Thread t1 = new Thread(my);
// Thread t2 = new Thread(my);
//Thread(Runnable target, String name)
Thread t1 = new Thread(my,"线程1");
Thread t2 = new Thread(my,"线程2");
//启动线程
t1.start();
t2.start();
}
}
实现多线程的方式三:实现Callable接口
- 这种方式实现多线程其实是方式二的优化,它可以获取方法的返回值。(FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况)
-
方法介绍
方法名 说明 V call() 计算结果,如果无法计算结果,则抛出一个异常(V表示返回call方法返回值的类型) FutureTask(Callable callable) 创建一个 FutureTask,一旦运行就执行给定的 Callable V get() 如有必要,等待计算完成,然后获取其结果(V表示返回call方法返回值的类型)
-
通过实现Callable接口实现多线程的实现步骤
- 自定义线程类的实现:
- 第一步:自定义一个类(MyCallable)实现Callable接口
- 第二步:在自定义类(MyCallable)中重写call()方法
- 自定义线程类的使用:(写一个测试类)
- 创建自定义类(MyCallable)的对象
- 创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
- 创建Thread类的对象,把FutureTask对象作为构造方法的参数
- 调用start方法(启动线程)
- 再调用get方法,就可以获取线程结束之后的结果(不一定要获取结果,可选)
- 自定义线程类的实现:
-
代码实现
- 1、自定义线程类
// Callable<String>这里的泛型对应call方法的返回值
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println("跟女孩表白" + i);
}
//返回值就表示线程运行完毕之后的结果
return "答应";
}
}
- 2、编写测试类
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1、创建自定义线程类
MyCallable mc = new MyCallable(); //线程开启之后需要执行里面的call方法
//2、创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
FutureTask<String> ft = new FutureTask<>(mc);
//3、创建Thread类的对象,把FutureTask对象作为构造方法的参数
Thread t1 = new Thread(ft);
// 获取线程结束之后的结果
String s = ft.get();
//开启线程
t1.start();
//String s = ft.get();
System.out.println(s);
}
}
三种实现多线程方式的对比
- 实现Runnable、Callable接口
- 好处: 扩展性强,实现该接口的同时还可以继承其他的类,可以共享一个 target 对象(即Runnable或者是Callable接口对象),所以非常适合多个相同线程来处理同一份资源的情况,从而可以将 CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
- 缺点: 编程相对复杂,要额外创建对象(Runnable或者是Callable对象),不能直接使用Thread类中的方法,需要使用Thread.currentThread()方法调用
- 继承Thread类
- 好处: 编程比较简单,可以直接使用Thread类中的方法
- 缺点: 可以扩展性较差,不能再继承其他的类