• 创建线程:Thread类和Runnable接口


    Java知识点总结:想看的可以从这里进入

    9、线程


    9.1、线程和进程

    进程是电脑级别的概念,它是具有一定独立功能的程序的一次执行过程,是正在运行的一个程序,而进程是系统进行资源分配和调度的一个独立单位。(简单说一个进程就相当于一个正在运行的app,而一个进程就是正在运行app的其中一个执行路径)

    image-20230205160450894 image-20230205160543617

    线程是基于进程来讲的,线程是进程的一个实体,是程序执行的最小单位,一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。一个进程可以同时执行多个线程。线程可以让程序并行执行。

    1. 进程是资源分配最小单位,线程是程序执行的最小单位;
    2. 进程有自己独立的地址空间,每启动一个进程,系统都会为其分配地址空间,建立数据表来维护代码段、堆栈段和数据段。所以多进程程序更安全,生命力更强,一个进程死掉不会对另一个进程造成影响
    3. 线程没有独立的地址空间,它使用相同的地址空间共享数据、所以多线程程序不易维护,一个线程死掉,整个进程就死掉了;
    4. 创建一个线程比进程开销小,线程占用的资源要⽐进程少很多,CPU切换一个线程比切换进程花费小
    5. 线程之间通信更方便,同一个进程下,线程共享全局变量,静态变量等数据,进程之间的通信需要以通信的方式(IPC)进行
    6. 进程对资源保护要求高,开销大,效率相对较低,线程资源保护要求不高,但开销小,效率高,可频繁切换;

    一个程序在运行时,允许同一时间并行执行多个进程,那它就是就是支持多线程的。我们平时运行的Java程序,最少也会有三个线程:main()方法主线程、gc垃圾回收线程、异常处理线程。

    9.2、线程的实现

    Java的JVM允许程序运行多个线程,它通过 java.lang.Thread 类来体现。

    image-20230207150436998

    9.2.1、Thread类
    1. 创建一个类去继承Thread类:

    2. 然后重写run方法:线程执行的方法

      //编写一个打印机类,继承Thread类
      public class MyPrinter extends Thread{
          private  String name;
          public Printer(String name) {
              this.name = name;
          }
          //编写打印方法
          public void print(){
              for (int i = 0; i <5; i++) {
                  System.out.println(name+"打印机:"+i);
              }
          }
          //线程执行的方法
          @Override
          public void run() {
              print();
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
    3. 创建对象,调用start方法:提示线程可以开始执行了,并执行后续的代码。线程执行时,执行的是run方法。

      run()方法需要由JVM调用,才会开启一个线程,但是这个线程什么时候执行、怎么执行都不知道,只是表示线程可以开始执行了,需要分配CPU等资源后才会执行。

      一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上的异常“IllegalThreadStateException”。

    在非线程执行的时候会按代码顺序依次执行

    public class TestThread {
        public static void main(String[] args) {
            Printer printer1 = new Printer("1号");
            Printer printer2 = new Printer("2号");
            printer1.print();
            System.out.println("1号打印机执行完毕");
            printer2.print();
            System.out.println("2号打印机执行完毕");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    image-20230205161539211

    运行线程执行打印:

    public class TestThread {
        public static void main(String[] args) {
            Printer printer1 = new Printer("1号");
            Printer printer2 = new Printer("2号");
            printer1.start();
            System.out.println("1号打印机执行完毕");
            printer2.start();
            System.out.println("2号打印机执行完毕");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    image-20230205161753391
    9.2.2、Runnable接口
    1. 编写类实现Runnable接口

    2. 实现run方法

      public class Printer implements Runnable{
          public void print(){
              for (int i = 0; i < 5; i++) {
                  System.out.println(Thread.currentThread().getName()+"打印机:"+i);
              }
          }
          @Override
          public void run() {
              print();
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
    3. 通过Thread类含参构造器创建线程对象,将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中,然后调用start方法

      image-20230206141522044

      真正的线程对象是Thread的实例(负责调用start启动线程),而实现了Runnable接口的类,其实例对象是作为Thread的一个属性进行传递的,其run()方法仅仅作为线程执行体。所以要获取当前线程对象只能通过Thread.currentThread()方法。

      public class TestThread {
          public static void main(String[] args) {
              Printer printer = new Printer();
      
              Thread th1 = new Thread(printer);
              th1.setName("1号");
              th1.start();
      
              Thread th2 = new Thread(printer);
              th2.setName("2号");
              th2.start();
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      image-20230205164343416

    使用匿名内部类:

    • 如果某个线程我们确定了仅仅只是使用一次就不再使用了,那么专门创建一个类并不是非常合适,所以可以使用匿名内部类的方式创建线程(要确定这个线程在其他地方不再使用)

      new Thread(new Runnable() {
          @Override
          public void run() {
              int cycles = 10;
              for(int i=0; i<cycles; i++){
                  System.out.println("打印:"+i);
              }
          }
      }).start();
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      image-20220809164600086
    • 从JAVA8开始,Runnable接口使用了@FunctionlInterface修饰,可以使用Runnable表达式。更加快捷

      new Thread(() -> {
          int cycles = 10;
          for(int i=0; i<cycles; i++){
              System.out.println("打印:"+i);
          }
      }).start();
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    9.2.3、Callable接口

    JDK5.0 提供了一个Callable接口来实现线程。

    1. 编写一个类实现Callable

    2. 实现call方法(相当于run方法),方法内是线程的循环体,可以有返回值、抛出异常

      public class CallableTest implements Callable<Integer> {
          private int sum;
          @Override
          public Integer call() throws Exception {
              for (int i=0; i<=5; i++){
                  sum+=i;
              }
              return sum;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
    3. 需要使用FutureTask类创建对象,此对象可以获取call方法的返回值,此方法底层是:Runnable, Future两个接口

      image-20230207150902583 image-20230207150931061
    4. 创建 Thread对象,将FutureTask对象当做参数传递

    5. 可以利用FutureTask提供的get方法获取返回值(这就是比使用Runnable接口有优势的地方)

      public static void main(String[] args) {
          CallableTest test = new CallableTest();
          //创建FutureTask对象
          FutureTask<Integer> task = new FutureTask<>(test);
          //启动线程
          new Thread(task).start();
          //可以获取返回值,也可以不使用
          try {
              Integer sum = task.get();
              System.out.println(sum);
          } catch (InterruptedException | ExecutionException e) {
              throw new RuntimeException(e);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      image-20230207151235823

  • 相关阅读:
    SAP 异常现象之同一个IDoc可以被POST两次触发2张不同的物料凭证
    Python每日一练(牛客数据分析篇新题库)——第31天:中位函数
    文档理解的新时代:LayOutLM模型的全方位解读
    组播分片报文重组后丢包问题
    Python学习:len() 函数详解:获取字符串长度或字节数、join()方法:合并字符串
    2023年天津中德应用技术大学专升本飞行器制造工程专业考试大纲
    商用工程运输车辆智能交通精细数字化管理中的大数据应用
    jeecgboot 使用阿里图标库
    dfs之字符串拼接
    Javascript学习之路--基础篇
  • 原文地址:https://blog.csdn.net/yuandfeng/article/details/126559264