一.多线程共有3种实现方式:

二.第一种实现方式:继承Thread类的方式进行实现
1.Thread类详解:

如上图,其中解释到线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程,意思是Thread类就表示Java里的一个线程,如果想要拥有一条线程的话,就可以创建Thread类对象并开启它即可。
具体实现步骤如下:
多线程的第一种启动方式:
* 步骤一:自己定义一个类继承Thread类
* (Thread类Java已经定义好,表示线程,因此自己定义的类也表示线程,因为是Thread类的子类)
* 步骤二:重写run方法
* 步骤三:创建子类的对象并启动线程
2.实例:

如上图,
该创建新执行线程的方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例即接下来可以创建子类的对象。例如,计算大于某一规定值的质数的线程可以写成如上图的代码 ,调用start方法来开启线程后就会自动去找对应的run方法,去执行run方法里的代码,
再举一个例子:
java
package com.itheima.a01threadcase1;
public class MyThread extends Thread{
//重写run方法,并书写线程要执行的代码
@Override
public void run() {
/*开始书写线程要执行的代码*/
for (int i = 0; i < 5; i++) {
System.out.println("Hello World");
}
}
}
java
package com.itheima.a01threadcase1;
public class ThreadDemo {
public static void main(String[] args) {
/*
* 多线程的第一种启动方式:
* 步骤一:自己定义一个类继承Thread类
* (Thread类Java已经定义好,表示线程,因此自己定义的类也表示线程,因为是Thread类的子类)
* 步骤二:重写run方法
* 步骤三:创建子类的对象并启动线程
*
* */
//1.创建Thread类的子类MyThread的对象
MyThread t1=new MyThread();
//2.启动线程
/*注:启动线程不能直接调用run方法,
如果直接调用run方法,仅仅是调用了一个方法,与之前的单线程程序是一样的,
正确的操作是调用start方法启动线程,start方法才表示开启线程,
之后也就无需调用run方法了,因为调用start方法后Java会自动去找对应的run方法,去执行run方法里的代码*/
t1.start();
}
}
这里可能会有疑问,上述代码与之前的创建对象并调用方法有什么区别呢?
此时还没有什么区别,因为现在只创建了一个线程对象,
如果现在再创建第二个线程对象,并启动第二个线程对象,如下图:

此时运行上图的程序,就会出现一会儿执行第一条线程t1,一会儿执行第二条线程t2,由于t1和t2执行的代码都是run方法里的内容,而且都是打印Hello World,
所以此时直接运行的话是无法分清谁是哪个线程打印的,
所以在这儿还需要额外的做一些处理,给t1和t2这两个线程分别起一个名字,并在run方法里把对应的名字写上,代码如下:
java
package com.itheima.a01threadcase1;
public class MyThread extends Thread{
//重写run方法,并书写线程要执行的代码
@Override
public void run() {
/*开始书写线程要执行的代码*/
for (int i = 0; i < 99; i++) {
/*这里多循环几次,为了更好的看出进程交替进行,
循环次数少可能看不出,因为循环次数少可能在进程数的最大范围内,可以不交替就同时进行*/
//getName方法用于获取线程的名字,为了区分线程进行过程,子类MyThread可直接调用父类Thread的方法
//哪个线程调用run方法,getName方法获取的就是哪个线程的名字
System.out.println( getName()+"->Hello World");
}
}
}
java
package com.itheima.a01threadcase1;
public class ThreadDemo {
public static void main(String[] args) {
/*
* 多线程的第一种启动方式:
* 步骤一:自己定义一个类继承Thread类
* (Thread类Java已经定义好,表示线程,因此自己定义的类也表示线程,因为是Thread类的子类)
* 步骤二:重写run方法
* 步骤三:创建子类的对象并启动线程
*
* */
//1.创建Thread类的子类MyThread的对象
MyThread t1=new MyThread();
MyThread t2=new MyThread();
/*给t1和t2分别起一个名字,为了区分两个线程*/
t1.setName("线程1");
t2.setName("线程2");
//2.启动线程
t1.start();
t2.start();
}
}
运行结果如下(只截取了一部分):

三.第二种实现方式:实现Runnable接口的方式进行实现
1.实现Runnable接口详解:

如上图,
创建线程的另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法,也就是说要自己定义一个类,去实现Runnable 这个接口,再去重写里面的run 方法即可(实现接口就要重写该接口的所有抽象方法,Runnable 接口里只有抽象方法run,所以重写run方法),在测试类中首先要创建自己的类,然后再创建Thread线程对象,把自己创建的对象传递给线程对象Thread后再调用start方法启动线程。
具体实现步骤如下:
多线程的第二种启动方式:
* 步骤一:自己定义一个类实现Runnable接口
* 步骤二:重写里面的run方法
* 步骤三:创建自己的类对象
* 步骤四:创建一个Thread类对象,并开启线程
2.例一:只创建一个线程对象
代码:
java
package com.itheima.a02threadcase2;
public class ThreadDemo {
public static void main(String[] args) {
/*
* 多线程的第二种启动方式:
* 步骤一:自己定义一个类实现Runnable接口
* 步骤二:重写里面的run方法
* 步骤三:创建自己的类对象
* 步骤四:创建一个Thread类对象,并开启线程
* */
//1.创建自己的类MyRun对象
/*MyRun类此时表示多线程要执行的任务*/
MyRun mr=new MyRun();
//2.创建线程对象
/*当前线程对象要执行mr里的代码,所以把mr传递给线程即可*/
Thread t1=new Thread(mr);
//3.开启线程
t1.start();
}
}
java
package com.itheima.a02threadcase2;
public class MyRun implements Runnable{
@Override
public void run() {
/*run是一个抽象方法,实现接口,就要重写该接口的所有抽象方法*/
//书写线程要执行的代码
for (int i = 0; i < 100; i++) {
System.out.println("Hello World");
}
}
}
运行结果:

3.例二:创建多个线程对象
代码:
java
package com.itheima.a02threadcase2;
public class ThreadDemo {
public static void main(String[] args) {
/*
* 多线程的第二种启动方式:
* 步骤一:自己定义一个类实现Runnable接口
* 步骤二:重写里面的run方法
* 步骤三:创建自己的类对象
* 步骤四:创建一个Thread类对象,并开启线程
* */
//1.创建自己的类MyRun对象
/*MyRun类此时表示多线程要执行的任务*/
MyRun mr=new MyRun();
//2.创建线程对象
/*当前线程对象要执行mr里的代码,所以把mr传递给线程即可*/
Thread t1=new Thread(mr);
Thread t2=new Thread(mr);
/*给线程对象设置名字,为了区分哪个线程正在进行*/
t1.setName("线程1");
t2.setName("线程2");
//3.开启线程
t1.start();
t2.start();
}
}
java
package com.itheima.a02threadcase2;
public class MyRun implements Runnable{
@Override
public void run() {
/*run是一个抽象方法(实现接口,就要重写该接口的所有抽象方法)*/
//书写线程要执行的代码
for (int i = 0; i < 100; i++) {
/*这里不能直接调用getName方法获取线程名字,因为getName方法位于Thread类,此时MyRun
* 只是实现了接口Runnable,Runnable接口没有getName方法,此时该怎么获取线程名字呢?
* 技巧:打印之前,先获取到当前线程的对象,可以使用静态方法currentThread*/
//currentThread方法的作用是返回当前正在执行的线程对象
Thread t = Thread.currentThread();
System.out.println(t.getName()+":Hello World");
//比如t1开始线程,调用start方法,自动执行run方法,首先获取到t1的名字线程1,再输出打印
//本例中有两个线程,每个线程循环遍历100个,共200个,只不过线程交替打印
}
}
}
运行结果:

四.Thread类常用方法:




五.第三种实现方式:利用Callable接口和Future接口方式实现
1.具体实现步骤如下:
多线程的第三种实现方式:
* 为什么要有多线程的第三种实现方式呢?其实是对多线程的第一、二种实现方式的补充->
* 第一种实现方式是继承Thread类并重写run方法,但run方法没有返回值,此时就无法获取多线程运行的结果;
* 同理第二种实现方式是实现了Runnable接口,并重写了抽象方法run,该run方法也没有返回值,
* 因此,如果此时要获取多线程运行的结果,第一、二种实现方式就无法做到,那么该怎么办呢?
* 因此就要用到多线程的第三种实现方式:
* 特点:多线程的第三种实现方式可以获取到多线程的运行结果
* 步骤如下:
* 步骤一:创建一个类如MyCallable实现Callable接口
* 步骤二:重写Callable接口里的所有抽象方法,Callable接口里的抽象方法只有call,因此重写call方法,注:call方法是有返回值的,这个返回值就表示多线程运行的结果
* 步骤三:创建刚才创建的类MyCallable的对象(表示多线程要执行的任务)
* 步骤四:创建FutureTask的对象(作用:管理多线程运行的结果->所以要想获取到多线程运行的结果,直接到创建FutureTask的对象里拿即可),
* 关键在于Future是一个接口,不能直接创建Future的对象,要创建Future的实现类的对象,FutureTask是Future的实现类对象
* 步骤五:创建Thread类的对象(表示线程),并启动线程。
2.实例:
java
package com.itheima.a03threadcase3;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/*
* 多线程的第三种实现方式:
* 为什么要有多线程的第三种实现方式呢?其实是对多线程的第一、二种实现方式的补充->
* 第一种实现方式是继承Thread类并重写run方法,但run方法没有返回值,此时就无法获取多线程运行的结果;
* 同理第二种实现方式是实现了Runnable接口,并重写了抽象方法run,该run方法也没有返回值,
* 因此,如果此时要获取多线程运行的结果,第一、二种实现方式就无法做到,那么该怎么办呢?
* 因此就要用到多线程的第三种实现方式:
* 特点:多线程的第三种实现方式可以获取到多线程的运行结果
* 步骤如下:
* 步骤一:创建一个类如MyCallable实现Callable接口
* 步骤二:重写Callable接口里的所有抽象方法,Callable接口里的抽象方法只有call,因此重写call方法,注:call方法是有返回值的,这个返回值就表示多线程运行的结果
* 步骤三:创建刚才创建的类MyCallable的对象(表示多线程要执行的任务)
* 步骤四:创建FutureTask的对象(作用:管理多线程运行的结果->所以要想获取到多线程运行的结果,直接到创建FutureTask的对象里拿即可),
* 关键在于Future是一个接口,不能直接创建Future的对象,要创建Future的实现类的对象,FutureTask是Future的实现类对象
* 步骤五:创建Thread类的对象(表示线程),并启动线程
* */
//1.创建刚才创建的类MyCallable的对象(表示多线程要执行的任务)
MyCallable mc=new MyCallable();
//2.创建Future接口的实现类对象FutureTask,用于管理多线程运行的结果
/*FutureTask对象的泛型表示多线程运行的结果,本例中多线程运行的结果为Integer型*/
FutureTask<Integer> ft=new FutureTask<>(mc);
/*mc传入,表示现在要用FutureTask对象去管理mc的结果*/
//3.创建线程对象
Thread t1=new Thread(ft);
/*该线程要执行ft,所以传入ft*/
//4.启动线程
t1.start();
//5.获取到多线程运行的结果
/*该如何获取呢?刚才说了,FutureTask就是用来管理线程运行结果的,
* 所以可以直接用FutureTask类里的get方法来获取线程的结果,
* get方法返回值的类型就是线程运行结果的类型,此时是Integer型*/
Integer result = ft.get();
//6.输出
System.out.println(result);
}
}
java
package com.itheima.a03threadcase3;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
/*Callable接口是有一个泛型的即Callable<V>,这个泛型V是什么意思呢?
* 注:第三种实现方式可以获取到多线程运行的结果,那么这里的泛型V表示线程的结果的类型,
* 比如想要开启一个线程,让它求1到100的整数和,最终的结果是整数,所以此时泛型V就可以写Integer*/
//重写抽象方法call,call方法的返回值与泛型V保持一致,因为call方法表示多线程运行的结果,泛型V也表示多线程运行的结果
@Override
public Integer call() throws Exception {
//求1到100之间的和
int sum=0;
for (int i = 1; i <= 100; i++) {
sum=sum+i;
}
return sum;
}
}
六.总结:

- 对于多线程的实现方式可以分为两类,本篇中第一、二种实现方式分为一类,无法获取到多线程的结果;第三种实现方式分为一类,此时可以获取到多线程的结果->所以如果要用到多线程的结果,就可以使用第三种实现方式,如果无需多线程的结果,可以选择第一或第二种实现方式
- 多线程的第一种实现方式与第二种实现方式的区别:第一种实现方式代码比较简单,而且是继承Thread类,所以在子类中可以直接使用Thread类里的方法,但可扩展性比较差,因为Java中是单一继承,此时继承了Thread类,就无法再继承其他的类了;第二种实现方式解决了第一种实现方式的缺点,第二种实现方式扩展性强,因为一个类可以实现多个接口,实现了Runnable接口后还可以实现其他接口,并且还可以继承其他的类,扩展性比较强,但代码相对比较复杂,因为此时只是实现了Runnable接口,并没有线程对象Thread类,也就不能直接使用Thread类中的方法,如果想用Thread类的方法,还需先获取到Thread类的对象,再调用Thread类的方法