概念

进程

  • 正在运行的程序,是系统进行资源分配和调用的独立单位
  • 每一个进程都有它自己的内存空间和系统资源,一个进程包括由操作系统分配的内存空间,包含一个或多个线程
  • 一个进程一直运行,直到所有的非守护线程都结束运行后才能结束

线程

  • 线程是进程中的单个顺序控制流,是一条执行路径
  • 一个进程如果只有一条执行路径,则称为「单线程程序」
  • 一个进程如果有多条执行路径,则称为「多线程程序」
  • 一个线程不能独立的存在,它必须是进程的一部分
  • 线程是 CPU 调度的最小单位

Java 中对于线程的描述是 Thread,其中,封装了线程的相关信息,最重要的有需要执行的任务的信息;

线程的交互

互斥和同步:

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。
  • 互斥是当一个线程正在运行时,其他的线程只能等待,当完成后就可以运行了。
  • 同步是两个或多个线程同时进行运行。

线程的状态

线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止

  • 创建(new)状态: 准备好了一个多线程的对象
  • 就绪(runnable)状态: 调用了 start() 方法, 等待 CPU 进行调度
  • 运行(running)状态: 执行 run() 方法
  • 阻塞(blocked)状态: 暂时停止执行, 可能将资源交给其它线程使用
  • 终止(dead)状态: 线程销毁

多线程的意义

  • 多进程的意义? 提高CPU的使用率
  • 多线程的意义? 提高应用程序的使用率

Java 程序运行原理

Java 命令会启动 Java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个「主线程」,然后主线程去调用某个类的 main 方法。

所以,main 方法运行在主线程中。未采用多线程时,程序都是单线程的。

扩展阅读

线程实现方式

线程创建方式——继承 Thread 类

  • 子类继承 Thread 类,子类中覆盖父类方法的 run 方法,将线程运行的代码放在 run 方法中;
  • 创建子类的实例,线程被创建;
  • 调用子类的实例的 start 方法,开启线程;
public class Test {
    public static void main(String[] args)  {
        System.out.println("主线程ID:"+Thread.currentThread().getId());
        MyThread thread1 = new MyThread("thread1");
        thread1.start();
        MyThread thread2 = new MyThread("thread2");
        thread2.run();
    }
}

class MyThread extends Thread{
    private String name;

    public MyThread(String name){
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println("name:"+name+" 子线程ID:"+Thread.currentThread().getId());
    }
}

执行结果:

主线程ID:1
name:thread2 子线程ID:1
name:thread1 子线程ID:10

同个线程的 start 方法重复调用的话,会出现 java.lang.IllegalThreadStateException 异常

线程创建方式——实现 Runnable 接口

  • 子类实现 Runnable 接口,覆盖接口中的 run 方法;
  • 通过 Thread 类创建线程,并将实现了 Runnable 接口的子类对象作为 Thread 类的参数;
  • 通过 Thread 类的实例对象调用 start 方法,开启线程;
public class Test {
    public static void main(String[] args)  {
        System.out.println("主线程ID:"+Thread.currentThread().getId());
        MyRunnable runnable1 = new MyRunnable("thread1");
        Thread thread1 = new Thread(runnable1);
        thread1.start();
        MyRunnable runnable2 = new MyRunnable("thread2");
        Thread thread2 = new Thread(runnable2);
        thread2.run();
    }
}

class MyRunnable implements Runnable{
    private String name;

    public MyRunnable(String name){
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println("name:"+name+" 子线程ID:"+Thread.currentThread().getId());
    }
}

执行结果:

主线程ID:1
name:thread2 子线程ID:1
name:thread1 子线程ID:10

不管是扩展 Thread 类还是实现 Runnable 接口来实现多线程,最终还是通过 Thread 的对象的API 来控制线程的,熟悉 Thread 类的 API 是进行多线程编程的基础。

在 Java 中,这 2 种方式都可以用来创建线程去执行子任务,具体选择哪一种方式要看自己的需求。直接继承 Thread 类的话,可能比实现 Runnable 接口看起来更加简洁,但是由于 Java 只允许单继承,所以如果自定义类需要继承其他类,则只能选择实现 Runnable 接口。

线程创建方式——实现 Callable 接口

还有一种是实现 Callable 接口,并与 Future、线程池结合使用

Thread 常用方法

 Java 多线程学习笔记 随笔

  • getName() 获取当前线程的名称 Thread.currenthrad().getName
继承Thread类,getName()获取当前线程名称。

实现Runnable接口,Thread.currentThread().getName();获取当前线程名称。
  • Thread.sleep(1000) 休眠 1 秒;

FAQ

启动一个线程是 run() 还是 start()?它们的区别?

start() 启动一个线程;

  • run 方法封装了被线程执行的代码,直接调用仅仅是普通方法的调用;
  • start 启动线程,并有 JVM 自动调用 run 方法;

Thread和Runnable的区别

如果一个类继承 Thread,则不适合资源共享。但是如果实现了 Runable 接口的话,则很容易的实现资源共享。

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

  1. 适合多个相同的程序代码的线程去处理同一个资源
  2. 可以避免 Java 中的单继承的限制
  3. 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
  4. 线程池只能放入实现 Runablecallable 类线程,不能直接放入继承 Thread 的类

提醒一下大家:main 方法其实也是一个线程。在 Java 中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到 CPU 的资源。

在 Java 中,每次程序运行至少启动 2 个线程。一个是 main 线程,一个是垃圾收集线程。因为每当使用 Java 命令执行一个类的时候,实际上都会启动一个 JVM,每一个 JVM 实际就是在操作系统中启动了一个进程。

参考

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄