• java 多线程学习


    一、如何实现多线程

    • 实现 Runnable 接口

      1 public static void main(String[] args) {
      2     MyThread myThread = new MyThread();// 一个实现了Runnable接口的类
      3     Thread t = new Thread(myThread);// 声明一个线程
      4     t.start();// 启动线程
      5 }
      6 
      7 public class MyThread implements Runnable {
      8     @Override
      9     public void run() {// 启动线程后执行的方法
     10         System.out.print("run the "run()" method!");
     11     }
     12 }
    • 继承 Thread 类

      1 public static void main(String[] args) {
      2     MyThread myThread = new MyThread();// 一个继承了Thread类的类
      3     myThread.start();// 启动线程
      4 }
      5 
      6 public class extends Thread {
      7     @Override
      8     public void run() {// 启动线程后执行的方法
      9         System.out.print("run the "run()" method!");
     10     }
     11 }
    • 使用 ExecutorService、Future 和 Callable 创建有返回值的线程

      1 public static void main(String[] args) {
      2     // 创建一个ExecutorService
      3     ExecutorService executorService =
      4             Executors.newCachedThreadPool();
      5     // new一个MyThread线程,并交给executorService执行,
      6     // 通过Future接收返回结果
      7     Future future =
      8             executorService.submit(new MyThread());
      9     try {
     10         // 从future中获取返回值
     11         Object result = future.get();
     12         System.out.println(result.toString());
     13     } catch (Exception e) {
     14         e.printStackTrace();
     15     }
     16 }
     17 
     18 // 一个有返回值的线程
     19 public class MyThread implements Callable {
     20     @Override
     21     public String call() {
     22         System.out.print("run the "call()" method!");
     23         return "test";
     24     }
     25 }

    二、实质


      1 Runnable、Callable 接口和多线程的实现没有关系
      2 接口的作用是约束行为,Runnable 接口的作用是指定一个协议, 规定所有继承这些接口的类都要有一个无参无返回值的方法
      3 多线程的实现是由类来完成的
      4 java 中所有的多线程最终都通过 Thread 类来实现, 线程的资源分配、线程启动这些工作都是在 start()方法中完成的, 严格来讲, 在 start0() 方法中完成的。start0() 是一个 native 方法, 并不是用 java 语言实现的。
      • 可以通过两种方式来定义多线程中要完成的工作

        • 实现 Runnable 接口
          Runnable 接口中的 run() 方法, 没有返回值
        • 实现 Callable 接口
          Callable 接口中的 call() 方法, 有返回值
        1. 需要对线程进行管理 (提交、启动、终止)

          1. Thread 类
            • Thread 类有一个构造函数可以传递一个 Runnable 类型的参数 target, 可以通过 target 来提交想要执行的任务 (实现 run() 方法, 然后传给 Thread 实例)
            • Thread 类本身实现了 Runnable 接口, 也可以直接通过实现 Thread 类来提交想要执行的任务 (重写 run() 方法)
            • Thread 类的 start()方法负责启动执行线程, 当 start() 方法执行时
              • 若已经重写了 run() 方法来执行任务, 则会执行该方法
              • 若传入了 target 参数, 则会调用 target 中的 run() 方法
            • Thread 类中有 stop 方法负责停止线程, 但是已经弃用
            • 可以通过 interrupt() 方法中断线程
          2. ExecutorService 接口
            • 可以通过 executor()方法或 submit() 方法提交任务
            • submit() 方法提交的任务会返回一个 Future 对象, 可以通过这个对象来获取返回结果
            • shutdown()和 shutdownNow() 方法可以用来停止线程池

        三、详解

          • 线程的生命周期

            • new thread(新建):创建一个线程实例, 比如通过 new 操作创建一个 Thread 类的实例, 此时线程未被启动
            • runnable(可运行):一个线程创建好之后, 需要通知 cpu 这个线程可以开始执行了, 比如 thread 类的 start() 方法执行后, 此时线程在就绪队列中等待 cpu 分配资源
            • running(运行中):线程获得 cpu 资源后开始运行, 比如运行 run() 方法中的逻辑, 此时除非线程自动放弃 cpu 资源或者有优先级更高的线程进入, 否则将执行到线程结束
            • dead(死亡):线程正常执行结束, 或者被 kill 调, 此时线程将不会再次被执行
            • block(阻塞):线程主动让出 cpu 使用权、其它更高优先级的线程进入、该线程的时间片用完,但此时该线程还没有执行完成, 都会使线程进入 block 状态, 进入 block 状态的线程还可以回到就绪队列中等待再次执行。
            • Thread 类中的方法

              • start:启动一个线程, 这个方法会是线程进入 Runnable 状态, 等待执行
              • isAlive:判断线程是否处于活动状态(Runnable 或 running)
              • sleep:强制让线程放弃当前时间片进入休眠状态一定时间, 此时线程会进入 block 状态, 直到休眠的时间结束, 再进入 Runnable 状态。sleep 是静态方法, 只能控制所在线程。sleep(0) 会直接触发下一次 cpu 竞争, 如果没有优先级更高的线程, 则会继续工作
              • wait(override Object):放弃对象锁, 进入等待池, 只有针对此对象调用 notify() 方法之后, 才会再次进入 Runnable 状态
              • join:阻塞等待线程结束, 可以接收参数 millis 和 nanos 指定等待的最大时间
              • interrupt: 中断线程, 这个方法并不能中断正在运行的线程, 运行该方法后, 只有当线程被 join(),sleep() 和 wait() 方法所阻塞时, 才会被 interrupted 方法所中断, 并抛出一个 InterruptedException 异常
              • static yield:主动放弃 cpu 使用权, 回到 Runnable 状态

            四、Tips

            1. block 状态
              1. 等待阻塞:运行线程执行了 wait 方法, 该线程会释放占用的所有资源包括对象锁, 进入等待队列中。进入等在队列的线程是不能自动唤醒的, 必须依靠其它线程调用 notify()、notifyAll() 来进行唤醒(该状态下线程会释放对象锁)
              2. 同步阻塞:运行的线程在获取对象同步锁时, 同步锁已被其它线程占用, 则该线程会进入锁队列等待获取同步锁, 直到获取到同步锁之后回再次进入 Runnable 状态(该状态下线程还没有获得对象锁)
              3. 其它阻塞:运行的线程调用了 sleep()或 join() 方法, 或者发出 IO 请求, 该线程会进入阻塞状态, 知道 sleep 超时、join 所等待的线程结束或是 IO 操作完成, 则会再次进入 Runnable 状态(该状态下线程只会放弃 cpu 而不会释放对象锁)
            2. sleep(0)
              sleep(0) 会重新触发一次 cpu 竞争, 当 Runable 队列中有大于或等于当前线程优先级的线程时, 当前线程会进入 Runnable 队列将 cpu 的使用权让出, 否则会继续运行
            3. sleep()和 wait()
              • sleep 方法会让出 cpu, 但不会释放对象锁, 等到 sleep 超时之后会自动进入 Runnable 队列
              • wait 方法会让出 cpu, 并释放对象锁, 需要其它线程调用 notify()、notifyAll() 才能重新进入 Runnable 队列
            4. interrupt()
              interrupt 方法的作用更倾向于告诉线程, 你可以结束了, 而不是直接地中断线程, 知道线程进入阻塞状态时, 才能中断线程。对于陷入死循环、IO 等待等难以进入阻塞状态的线程来说,interrupt 方法是不能有效中断的。
            5. sleep()和 yield()
              这两个方法都会让出 cpu 使用权,sleep 会进入 block 状态, 而 yield 会直接进入 Runnable 状态


              本文出自:hacpai:Zl992532172

          • 相关阅读:
            Delphi中创建一个可以改变大小的无边框窗口
            Delphi中让窗口关闭按钮无效的6种方法
            把人笑抽筋的签名
            Delphi中判断窗体最大化和最小化事件
            Delphi中窗体的帮助按钮上执行一个自定义的动作
            无法查询部门收支分析表
            Delphi中去掉限制窗体最小尺寸的Windows约束
            Delphi中在窗体标题栏画自定义文字
            调拨单等单据定位功能没有过滤条件
            Form中对象的引用
          • 原文地址:https://www.cnblogs.com/ios9/p/7473148.html
          Copyright © 2020-2023  润新知