4.3.多线程&JUC-多线程的实现方式

一.多线程共有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类的方法

相关推荐
梦6503 小时前
网络传输七层协议
开发语言·网络·php
Knight_AL3 小时前
Spring Boot 事件机制详解:原理 + Demo
java·数据库·spring boot
南 阳3 小时前
Python从入门到精通day16
开发语言·python·算法
李少兄3 小时前
Java 后端开发中 Service 层依赖注入的最佳实践:Mapper 还是其他 Service?
java·开发语言
jiaguangqingpanda3 小时前
Day29-20260125
java·数据结构·算法
不会c+3 小时前
@Controller和@RequestMapping以及映射
java·开发语言
1登峰造极4 小时前
uniapp 运行安卓报错reportJSException >>>> exception function:createInstanceContext, exception:white screen
android·java·uni-app
難釋懷4 小时前
解决状态登录刷新问题
java·开发语言·javascript
ytttr8734 小时前
基于MATLAB的三维装箱程序实现(遗传算法+模拟退火优化)
开发语言·matlab