JAVA多线程基础

Posted 李维维

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA多线程基础相关的知识,希望对你有一定的参考价值。

学习技术的步骤

场景-->需求-->解决方案-->应用-->原理

一、多线程的发展历史

  • 真空管和穿孔打卡

    操作员在机房里面来回调度资源,以及计算机同一个时刻只能运行一个程序,在程序输入的过程中,计算机计算机和处理空闲状态 。而当时的计算机是非常昂贵的,人们为了减少这种资源的浪费。就采用了 批处理系统来解决

  • 晶体管和批处理系统

    批处理操作系统虽然能够解决计算机的空闲问题,但是当某一个作业因为等待磁盘或者其他 I/O 操作而暂停时,那CPU 就只能阻塞直到该 I/O 完成,对于 CPU 操作密集型的程序,I/O 操作相对较少,因此浪费的时间也很少。但是对于 I/O 操作较多的场景来说,CPU 的资源是属于严重浪费的

  • 集成电路和多道程序设计

    有了进程以后,可以让操作系统从宏观层面实现多应用并发。而并发的实现是通过 CPU 时间片不端切换执行的。对于单核 CPU 来说,在任意一个时刻只会有一个进程在被CPU 调度

二、线程

  1. 在多核 CPU 中,利用多线程可以实现真正意义上的并行执行

  2. 在一个应用进程中,会存在多个同时执行的任务,如果其中一个任务被阻塞,将会引起不依赖该任务的任务也

被阻塞。通过对不同任务创建不同的线程去处理,可以提升程序处理的实时性

  1. 线程可以认为是轻量级的进程,所以线程的创建、销毁比进程更快

创建线程的四种方式

  • 继承 Thread 类创建线程
  • 实现 Runnable 接口创建线程
  • 实现 Callable 接口通过 FutureTask 包装器来创建 Thread 线程
  • 通过线程池创建线程

三、线程的六种状态

  • 线程一共有 6 种状态(NEW、RUNNABLE、BLOCKED、WAITING、TIME_WAITING、TERMINATED)以枚举的方式存储在Thread类内部

    • NEW:初始状态,线程被构建,但是还没有调用 start 方法RUNNABLED:运行状态,JAVA 线程把操作系统中的就绪和运行两种状态统一称为“运行中”
    • BLOCKED:阻塞状态,表示线程进入等待状态,也就是线程因为某种原因放弃了 CPU 使用权,阻塞也分为几种情况:
      • 等待阻塞:运行的线程执行 wait 方法,jvm 会把当前线程放入到等待队列
      • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被其他线程锁占用了,那么 jvm 会把当前的线程放入到锁池中
      • 其他阻塞:运行的线程执行 Thread.sleep 或者 t.join 方法,或者发出了 I/O 请求时,JVM 会把当前线程设置为阻塞状态,当 sleep 结束、join 线程终止、io 处理完毕则线程恢复
    • TIME_WAITING:超时等待状态,超时以后自动返回
    • TERMINATED:终止状态,表示当前线程执行完毕
  • 代码示例

    public class ThreadStatusDemo {
    
        public static void main(String[] args) {
            //新建线程执行sleep方法
            new Thread(()->{
                while(true){
                    try {
                        TimeUnit.SECONDS.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"Time_Waiting_Thread").start();
            //新建线程执行await方法,需要notify唤醒,进入阻塞状态
            new Thread(()->{
                while(true){
                    synchronized (ThreadStatusDemo.class) {
                        try {
                            ThreadStatusDemo.class.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            },"Wating_Thread").start();
    
        //添加通过锁,进行锁争抢  为争抢到锁的线程进入BLOCKED
            new Thread(new BlockedDemo(),"Blocke01_Thread").start();
            new Thread(new BlockedDemo(),"Blocke02_Thread").start();
        }
        static class BlockedDemo extends  Thread{
    
            @Override
            public void run() {
                synchronized (BlockedDemo.class){
                    while(true){
                        try {
                            TimeUnit.SECONDS.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
    

    启动一个线程前,最好为这个线程设置线程名称,因为这样在使用 jstack 分析程序或者进行问题排查时,就会给开发人员提供一些提示;

    显示线程所处状态(在idea的Terminal执行,target对应目录下)
    • 运行该示例,打开终端或者命令提示符,键入“jps”, (JDK1.5 提供的一个显示当前所有 java 进程 pid 的命令)
    • 根据上一步骤获得的 pid,继续输入 jstack pid(jstack是 java 虚拟机自带的一种堆栈跟踪工具。jstack 用于打印出给定的 java 进程 ID 或 core file 或远程调试服务的 Java 堆栈信息)

线程的启动和停止

  • 线程的启动(start()方法和run方法的联系和区别),进入start底层,会发现调用了native方法,也就是外部方法,jvm中实现的方法start0,它在底层实现了对run方法的调用;主线程可以调用run方法,不过那是调用实体类的方法,属于主线程,不属于多线程,多次调用同一线程的start方法,会出现场错误;

  • 线程的停止

    • interrupt() 方法

      通过interrupt() 方法,发出中断标志,程序根据标志判断是是否停止线程,interrupt()并不能停止线程,它和return,特定循环中的break可以停止到当前的线程

      public class StopThread extends  Thread {
          @Override
          public void run() {
              while (true){
                  if(Thread.currentThread().isInterrupted()){
                      System.out.println("线程被中断了");
                      break;
                  }
              }
              System.out.println("这是收到中断标志,跳出循环");
      
          }
      }
      
      //main方法
      public class Run {
          public static void main(String[] args) throws InterruptedException {
              Thread s = new StopThread();
              s.start();
              Thread.sleep(10);
              s.interrupt();
          }
      }
      

虽然中断标志收到跳出了循环,但是线程后面的程序仍然继续执行了
  • 抛异常

    public class StopThreadUseException extends  Thread {
        @Override
        public void run() {
            super.run();
            try {
                for (int i = 0; i <500000 ; i++) {
                    if(this.isInterrupted()){
                        System.out.println("线程被中断了");
                        throw new InterruptedException();
                    }
                    System.out.println("i:"+i);
                }
                System.out.println("for后面的代码");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //main方法
    public class Run {
        public static void main(String[] args) throws InterruptedException {
           // Thread s = new StopThread();
            Thread s = new StopThreadUseException();
            s.start();
            Thread.sleep(10);
            s.interrupt();
        }
    }
    

    运行结果:执行的线程收到信号,抛出异常,并且跳出for的代码没有继续执行

  • stop方法:不安全,资源为完全退出,多线程中会导致数据不一致(已执行代码所修改的类变量不会还原)

  • 中断复位

Thread.interrupted()是属于当前线程的,是当前线程对外界中断信号的一个响应,表示自己已经得到了中断信号,但不会立刻中断自己,具体什么时候中断由自己决定,让外界知道在自身中断前,他的中断状态仍然是 false,这就是复位的原因。,thread.interrupt()方法实际就是设置一个 interrupted 状态标识为 true、并且通过ParkEvent 的 unpark 方法来唤醒线程。

  1. 对于 synchronized 阻塞的线程,被唤醒以后会继续尝试获取锁,如果失败仍然可能被 park

  2. 在调用 ParkEvent 的 park 方法之前,会先判断线程的中断状态,如果为 true,会清除当前线程的中断标识

  3. Object.wait 、 Thread.sleep 、 Thread.join 会 抛 出InterruptedException?

    你会发现这几个方法有一个共同点,都是属于阻塞的方法,阻塞方法的释放会取决于一些外部的事件,但是阻塞方法可能因为等不到外部的触发事件而导致无法终止,所以它允许一个线程请求自己来停止它正在做的事情。当一个方法抛出 InterruptedException 时,它是在告诉调用者如果执行该方法的线程被中断,它会尝试停止正在做的事情并且通过抛出 InterruptedException 表示提前返回。所以,这个异常的意思是表示一个阻塞被其他线程中断了。然 后 , 由 于 线 程 调 用 了 interrupt() 中 断 方 法 , 那 么

    Object.wait、Thread.sleep 等被阻塞的线程被唤醒以后会通过 is_interrupted 方法判断中断标识的状态变化,如果发现中断标识为 true,则先清除中断标识,然后抛出InterruptedException

以上是关于JAVA多线程基础的主要内容,如果未能解决你的问题,请参考以下文章

java基础入门-多线程同步浅析-以银行转账为样例

java多线程基础

Java基础之多线程

Java多线程基础

多线程编程学习一(Java多线程的基础)

Java 多线程基础多线程的实现方式