【了解Java多线程】三种方式实现多线程


活动地址:CSDN21天学习挑战赛

目录

​一、辨析线程与进程

二、单线程运行和多线程运行的区别

三、线程创建

3.1 继承Thread类

3.2 实现Runnable接口

3.3 实现Callable接口

四、Thread 和Runnable的区别


​一、辨析线程与进程

进程Process 线程Thread
基本单位 系统资源分配的基本单位 CPU调度和执行的基本单位
状态 动态 静态
区别

①是可并发执行的具有独立功能的应用程序(相当于电脑可同时打开多个浏览器的多个界面)

②拥有运行空间和资源(相当于应用程序中的图像、声音、文字等)

①只是执行的代码

② 不具有资源

联系 一个进程中的每个任务称为一个线程

        线程的基本概念:

                1)线程就是独立的执行路径;
                2)在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程(用户线程),gc线程(守护线程);main()称之为主线程,为系统的入口,用于执行整个程序;
                3)在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,操作系统紧密相关的,先后顺序是不能人为干预的。
                4)对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;线程会带来额外的开销,如cpu调度时间,并发控制开销。

二、单线程运行和多线程运行的区别

                 单线程:进程由main()函数开始执行,只包含一个主线程

                多线程:在main()函数中创建多个线程对象,并调用start()函数启动并发执行

三、线程创建

3.1 继承Thread类

        1、步骤:①创建一个线程类(类声明为Thread子类)

                                                 class  线程类名称  extends  Thread

                   ②在该线程类重写run()方法

                                                public void run(){

                                                                ......;  //该线程想要执行的操作

                                                }

                  ③在main()函数中创建该线程类对象,并调用start()函数启动

                                                new 线程类().start();//函数 线程类() 是初始化线程类对象

      2、 示例:创建两个线程,观察多线程的运行次序的实现机制

//通过继承Thread类创建线程,“线程1”和“线程2”分别执行10次

package Thread;

public class TwoThreadsTest {
     public static void main (String[] args) {
         new SimpleThread("线程1").start();//虽然线程的定义位于run()方法里,线程的运行调用start(),相当于调用线程管理器
         //调用start相当于去线程管理器注册线程运行的方式及准备工作,主线程调用start(),子线程执行run()方法
         new SimpleThread("线程2").start();
      }
     }

class SimpleThread extends Thread {
     public SimpleThread(String str) {   //线程对象的初始化函数定义
         super(str);//继承父类Thread属性
     }
     public void run() {//重写run函数
         for (int i = 0; i < 10; i++) {//输出10次,getName:获得线程的名字
             System.out.println(i + " " + getName());
             try {
                 sleep((int)(Math.random() * 1000));//暂停随机的将近1s(sleep(1)是暂停1ms)
             } catch (InterruptedException e) {}//有异常干扰时,进程结束
         }
         System.out.println("DONE! " + getName());//获得线程名字
     }
 }

        运行结果:"线程1"和"线程2"交叉输出,说明两个线程是同时执行的;每一次点击运行时,各个线程输出顺序是不同(随机)的。

 

3.2 实现Runnable接口

        1、步骤:①创建一个线程类(该类是实现类Runnable接口)

                                                 class  线程类名称  implements  Runnable

                   ②在该线程类重写run()方法

                                                public void run(){

                                                                ......;  //该线程想要执行的操作

                                                }

                  ③在main()函数中创建该线程类对象,并类型转化为Thread类型,调用start()函数启动

                                                线程类  t = new 线程类();

                                                new Thread(t).start();

      2、同样的示例如下:

//通过实现Runnable类创建线程,“线程1”和“线程2”分别执行10次

package Thread;

public class ThreadRunnable2 {
     public static void main (String[] args) {
         
         SimpleThread t1 = new SimpleThread("线程1");
         SimpleThread t2 = new SimpleThread("线程2");
         
         new Thread(t1).start();   //强制类型转化,转化为Thread类型才能启动start()函数
         new Thread(t2).start();
      }
     }

class SimpleThread implements Runnable {
     String string;
     public SimpleThread(String str) {
         //由于没有“继承”的关系,不使用super,而是关键字this定义子类变量
         this.string = str;
     }
     public void run() {//重写run函数
         for (int i = 0; i < 10; i++) {//输出10次,由于没有“继承”的关系,不能使用Thread内置的getName函数获得变量名称
             System.out.println(i + " " + string);
             try {
                 Thread.sleep((int)(Math.random() * 1000));//sleep函数的使用继承Thread类,即Thread.sleep
             } catch (InterruptedException e) {}//有异常干扰时,进程结束
         }
         System.out.println("DONE! " + string);//获得线程名字
     }
 }

运行结果:

3.3 实现Callable接口

        1、步骤:①创建一个线程类,实现Callable接口,需要返回值类型

                                                 class  线程类名称  implements  Callable<返回值类型>

                        ②在该线程类重写call()方法,主函数声明时需要抛出异常

                                                public <返回值类型> call(){

                                                                ......;          //该线程想要执行的操作

                                                                try {          

                                                                ......;
                                                                     } catch (InterruptedException e) {}

                                                                return  r;

                                                }

                        ③在main()函数中创建该线程类对象通过创建执行服务启动(步骤④~⑦)

                                                线程类  t = new 线程类();

                        ④创建执行服务

                                                ExecutorService ser = Executors.newFixedThreadPool(n);   //n为线程池中线程的个数,ser为执行服务类对象

                        ⑤提交执行

                                                Future<返回值类型> result = ser.submit(t);

                        ⑥获取结果

                                                <返回值类型>   r = result.get();   //定义返回值变量,由return语句返回

                        ⑦关闭服务

                                                ser.shutdownNow();

        2、示例如下:

//通过实现Callable类创建线程,“线程1”和“线程2”分别执行10次

package Thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ThreadCallable {
     public static void main (String[] args) throws InterruptedException, ExecutionException {
         
         SimpleThread2 t1 = new SimpleThread2("线程1");
         SimpleThread2 t2 = new SimpleThread2("线程2");
         
        //创建执行服务(线程池创建2个线程)
         ExecutorService ser = Executors.newFixedThreadPool(2);
         
         //提交执行
         Future<Boolean> r1 = ser.submit((Callable<Boolean>) t1);
         Future<Boolean> r2 = ser.submit((Callable<Boolean>) t2);
         
         //获取结果
         boolean rs1 = r1.get();
         boolean rs2 = r2.get();

         //关闭服务
         ser.shutdown();
      }
     }

class SimpleThread2 implements Callable<Boolean> {  //这里假设返回的是布尔类型(不影响结果的输出)
     String string;
     public SimpleThread2(String str) {
         //由于没有“继承”的关系,不使用super,而是关键字this定义子类变量
         this.string = str;
     }
     public Boolean call() {//重写call函数
         for (int i = 0; i < 10; i++) {//输出10次,由于没有“继承”的关系,不能使用Thread内置的getName函数获得变量名称
             System.out.println(i + " " + string);
             try {
                 Thread.sleep((int)(Math.random() * 1000));//sleep函数的使用继承Thread类,即Thread.sleep
             } catch (InterruptedException e) {}//有异常干扰时,进程结束
         }
         System.out.println("DONE! " + string);//获得线程名字
         return true;
     }
 }

        3、运行结果:

四、Thread 和Runnable的区别

        若一个类继承Thread,则不适合资源共享;但是若一个类实现了Runable接口,容易实现资源共享

        总结:实现Runnable接口比继承Thread类所具有的优势

        1. 适合多个相同的程序代码的线程去共享同一个资源。
        2. 可以避免java中的单继承的局限性。
        3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
        4. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。

猜你喜欢

转载自blog.csdn.net/m0_46427461/article/details/126221367
今日推荐