Java - 多线程

多线程

什么是线程?

  • 单线程(Thread)是一个程序内部的一条执行流程。
  • 多线程 - 多条线程由CPU负责调度执行。
  • java是通过java.lang.Thread

java中创建线程的方式:

  1. 方式一:继承Thread类

    1. 定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法
    2. 创建MyThread类的对象
    3. 调用线程对象的start()方法启动线程(启动后还是执行run方法的)
    • 优缺点:
      • 优点:编码简单。
      • 缺点:由于java中是单继承的,只能认一个类做爸爸。线程类已经继承Thread,无法继承其他类,不利于功能的扩展。
    • 注意事项:
      • 启动线程必须调用start方法,不是调用run方法。
        • start() - 向CPU注册单独的执行流程
        • run() - 执行流程的执行内容
      • 不要把主线程任务放在启动子线程之前。
    • 示例:
      public class ThreadTest {
          // main方法是由一条默认的主线程负责执行
          public static void main(String[] args) {
              // 3. 创建MyThread线程类的对象代表一个线程
              Thread thread = new MyThread();
              // 4. 启动线程(程序会自动执行run方法)
              thread.start();
      
              for (int i = 1; i <= 5; i++) {
                  System.out.println("主线程main输出: " + i);
              }
          }
      }
      
      public class MyThread extends Thread {
          // 必须重写Thread类的run方法
          @Override
          public void run() {
              // 描述线程的执行任务
              for (int i = 1; i <= 5; i++) {
                  System.out.println("子线程MyThread线程输出:" + i);
              }
          }
      }
      ``
      
  2. 方式二:实现Runnable接口

    1. 定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
    2. 创建MyRunnable任务对象
    3. 把MyRunnable任务对象交给Thread处理
    4. 调用线程对象的start()方法启动线程
    • 优缺点:
      • 优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。
      • 缺点:需要多一个Runnable对象。
    • 示例:
      // 1. 定义一个任务类, 实现Runnable接口
      public class MyRunnable implements Runnable {
          // 2. 重写Runnable的run方法
          @Override
          public void run() {
              // 线程要执行的任务
              for (int i = 0; i <= 5; i++) {
                  System.out.println("子线程输出: ===> " + i);
              }
          }
      }
      
      public class main {
          public static void main(String[] args) {
              // 3. 创建任务对象
              Runnable runnable = new MyRunnable();
              // 4. 把任务对象交给一个线程对象处理
              new Thread(runnable).start();
      
              for (int i = 0; i <= 5; i++) {
                  System.out.println("主线程main输出: ==> " + i);
              }
          }
      }
      
  3. 方式二:匿名内部类、lambda写法

    1. 可以创建Runnable的匿名内部类对象
    2. 再交给Thread线程对象
    3. 再调用线程对象的start()启动线程
      示例:
      public class main {
          public static void main(String[] args) {
              // 1. 直接创建Runnable的匿名内部类形式(任务对象)
              Runnable runnable = new Runnable() {
                  @Override
                  public void run() {
                      for (int i = 1; i <= 5; i++) {
                          System.out.println("子线程1输出: " + i);
                      }
                  }
              };
              new Thread(runnable).start();
      
              // 简化形式 1
              new Thread(new Runnable() {
                  @Override
                  public void run() {
                      for (int i = 1; i <= 5; i++) {
                          System.out.println("子线程2输出: " + i);
                      }
                  }
              }).start();
      
              // 简化形式 2
              new Thread(() -> {
                  for (int i = 1; i <= 5; i++) {
                      System.out.println("子线程3输出: " + i);
                  }
              }).start();
      
              for (int i = 1; i <= 5; i++) {
                  System.out.println("主线程输出: " + i);
              }
          }
      }
      
  4. 方法三:实现Callable接口

    1. 创建任务对象
      • 定义一个类实现Callable接口,重写call方法,封装要做的事情和要返回的数据
      • 把Callable类型的对象封装乘FutureTask(线程任务对象)
    2. 把线程任务对象交给Thread对象
    3. 调用Thread对象的start方法启动线程
    4. 线程执行完毕后,通过FutureTask对象的get方法获取线程任务的执行结果。
      • FutureTask提供的构造器:public FutureTask<>(Callable call) - 把Callable对象封装成FutrueTask对象
      • FutureTask提供的方法:public V get() throws Exception - 获取线程执行call方法返回的结果
    • 优缺点:
      • 优点:
        • 前面两种线程创建方式都无法直接返回结果,Callable接口和FutureTask类来实现返回线程执行完毕后的结果。
        • 线程任务类只是实现接口,可以继续继承类和实现接口,扩展性更强。
        • 可以在线程执行完毕后获取线程执行的结果
      • 缺点:
    • 示例:
      // 1. 让这个类实现Callable接口
      public class CallableThread implements Callable<String> {
          private int n;
      
          public CallableThread(int n) {
              this.n = n;
          }
      
          // 2. 重写call方法
          @Override
          public String call() throws Exception {
              // 描述线程的任务,返回线程执行后返回的结果
              // 需求:求1-n的和返回
              int sum = 0;
              for (int i = 1; i <= n; i++) {
                  sum += i;
              }
              return "线程求出了1-" + n + "的和是:" + sum;
          }
      }
      
      public class main {
          public static void main(String[] args) throws ExecutionException, InterruptedException {
              // 3. 创建一个Callable的对象
              CallableThread callableThread = new CallableThread(100);
              // 4. 把Callable的对象封装乘一个FutureTask对象(任务对象)
              // 未来任务的作用?
              //  1. 是一个任务对象,实现了Runnable对象
              //  2. 可以在线程执行完成后,未来任务对象调用get方法获取线程执行完毕后的结果
              FutureTask<String> futureTask = new FutureTask<>(callableThread);
              // 5. 把任务对象交给一个Thread对象
              // 6. 获取线程执行完毕后返回的结果。
              // 注意:如果线程执行到此处,上边的线程还没有执行完毕,主线程会在改行代码暂停等待futureTask.get()的线程执行完毕
              new Thread(futureTask).start();
              System.out.println("futureTask.get()");
              System.out.println(futureTask.get());
          }
      }
      

Thread与线程操作相关的方法:

  1. public void run() - 线程的任务方法
  2. public void start() - 启动线程
  3. public String getName() - 获取当前线程的名称,线程名称默认是Thread-索引
  4. public void setName(String name) - 为线程设置名称
  5. public static Thread currentThread() - 获取当前执行的线程对象
  6. public static void sleep(long time) - 让当前线程休眠多少毫秒后,再继续执行
  7. public final void join()… - 让调用当前这个方法的线程先执行完
public class MyThread extends Thread {
    public MyThread(String name) {
        super(name);
    }

    public MyThread() {
    }

    @Override
    public void run() {
        Thread thread = Thread.currentThread();
        for (int i = 0; i < 5; i++) {
            System.out.println(thread.getName() + "输出:" + i);
        }
    }
}

public class main {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 5; i++) {
            System.out.println("测试休眠:" + i + "s");
            if (i == 3) {
                // 休眠5s
                Thread.sleep(5000);
            }
        }

        Thread thread = new MyThread("测试join方法的线程");
        thread.start();
        thread.join();

        Thread thread1 = new MyThread();
        thread1.setName("1号线程");
        thread1.start();
        System.out.println(thread1.getName()); // Thread-0

        Thread thread2 = new MyThread();
        thread2.setName("2号线程");
        thread2.start();
        System.out.println(thread2.getName()); // Thread-1

        Thread thread3 = new MyThread("2号线程");
        thread3.start();
        System.out.println(thread3.getName()); // Thread-1

        // 为什么子线程还没开始执行就已经输出了它的名字?
        // 原因:因为子线程start后还在执行准备工作,所以主线程继续执行,导致先输出了子线程的名字

        // 主线程对象的名字
        // 哪个线程执行它,它就会得到哪个线程
        Thread thread4 = Thread.currentThread();
        thread4.setName("主线程");
        System.out.println(thread4.getName()); // main

        for (int i = 0; i < 5; i++) {
            System.out.println("主线程输出:" + i);
        }
    }
}

Thread提供的常见构造器:

  1. public Thread(String name) - 可以为当前线程指定名称
  2. public Thread(Runnable target) - 封装Runnable对象成为线程对象
  3. public Thread(Runnable target, String name) - 封装Runnable对象成为线程对象,并指定线程名称

Thread其他方法的说明

Thread类还提供了诸如:yield、interrupt、守护线程、线程优先级等线程的控制方法

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值