第16章 多线程


本章将会介绍 Java 多线程编程的相关方面,包括创建、启动线程、控制线程,以及多线程的同步操作。并会介绍如何利用 Java 内建支持的线程池来提高多线程的性能。

线程概述

几乎所有的操作系统都支持同时运行的多个任务,一个任务通常就是一个程序,每个运行中的程序就是一个进程。当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程。

线程和进程

所有运行中的任务通常对应一条进程(process)。当一个程序进入内存中运行,即变成一个进程。进程是处于运行中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位。
进程包括如下三个特征:

  • 独立性
  • 动态性:进程与程序的区别在于,程序只是一个静态的指令集,而进程是一个正在系统中活动的指令集合。进程拥有自己的生命周期和不同的状态,这些概念在程序中是不具备的。
  • 并发行:多个进程可以在单处理器上并发进行,多个进程之间不会相互影响。

线程(Thread)也被称为轻量级进程,线程是进程的执行单元。线程在进程中是独立的、并发的执行流,每条线程也是相互独立的。线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须拥有一个父进程。
线程是独立运行的,它并不知道进程中是否还有其他线程存在。线程的执行是抢占式的,也就是说,当前运行的线程在任何时候都可能被挂起,以便另外一个线程可以运行。

多线程的优势

  • 进程间不能共享内存,但线程之间共享内存非常容易
  • 系统创建进程需要为进程重新分配系统资源,但创建线程则代价小得多,因此使用多线程来实现任务并发比多进程的效率高
  • Java 语言内置多线程功能支持

线程的创建和启动

Java 的 Thread 类代表线程,所有线程对象都必须是 Thread 类或其子类的实例。每条线程的作用是完成一定的任务,实际上就是执行一段程序流(一段顺序执行的代码)。Java 使用 run 方法来封装这样一段程序流。

继承 Thread 类创建线程类

通过继承 Thread 类来创建并且启动多线程的步骤:

  • 定义 Thread 类的子类,并重写该类的 run 方法,该 run 方法的方法体就代表线程需要完成的任务。
  • 创建 Thread 子类的实例,即创建了线程对象
  • 同线程对象的 start 方法来启动该线程
public class FirstThread extends Thread {
    private int i;
    //重写run()方法,方法体就是线程的执行体
    public void run(){
        for (; i < 100; i++) {
            System.out.println(getName() + " " + i);
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            //调用Thread的currentThread方法获取当前线程
            System.out.println(Thread.currentThread().getName() + " " + i);
            if(i == 20){
                //创建并启动第一条线程
                new FirstThread().start();
                new FirstThread().start();
            }
        }
    }
}

运行结果:
Thread-0 97
Thread-0 98
Thread-0 99
Thread-1 0
Thread-1 1
多条线程之间无法共享线程类的实例变量

使用 Runnable 接口或创建线程类

实现 Runnable 接口来创建并启动多线程的步骤如下:

  • 定义 Runnable 接口的实现类,并重写该接口的 run() 方法
  • 创建 Runnable 实现类的实例,并以此实例作为 Thread 的 target 来创建 Thread 对象,该 Thread 对象才是真正的线程对象。
  • 使用 start() 来启动该线程
public class SecondThread implements Runnable {
    private int i;

    @Override
    public void run() {
        for (; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if(i == 20){
                SecondThread st = new SecondThread();
                new Thread(st, "新线程1").start();
                new Thread(st, "新线程2").start();
             }
        }
    }
}

运行结果:
新线程1 14
新线程1 15
新线程1 16
新线程1 17
新线程1 18
新线程1 19
采用 Runnable 接口的方式来创建多条线程是可以共享线程类的实例属性的。

两种方式创建线程的对比

通过继承 Threa 类和 实现 Runnable 接口都可以实现多线程,但两种方式存在一定的差别

采用实现 Runnable 接口的方式的多线程:

  • 线程类只是实现了 Runnable 接口,还可以继承其他类
  • 这中方式下,可以多个线程共享一个 target 对象,所以非常适合多个相同线程来处理同一份资源的情况

采用继承 Thread 类的方式的多线程:

  • 因为线程已经继承了 Thread 类,所以不能再继承其他的父类
  • 编写简单

线程的生命周期

当线程被创建并启动以后,它既不是已启动就进入执行状态,也不是一直处于执行状态,在线程的生命周期中,它要经过新建(new)、就绪、运行、阻塞和死亡五种状态。

新建和就绪状态

当程序使用 new 关键字创建了一个线程之后,该线程就处于新建状态,此时它与其它 Java 对象一样,仅仅由 Java 虚拟机为其分配了内存,并初始化了其成员你变量。当程序调用了 start() 方法之后,该线程处于就绪状态,Java 虚拟机会为其创建方法调用栈和程序计数器,处于这个状态的线程并没有开始运行,它只是表示该线程可以运行了。

运行和阻塞状态

如果处于就绪状态的线程获取了 CPU,开始执行 run 方法的线程执行体,则该线程处于运行状态。
发生如下情况,系统将会进入阻塞状态:

  • 线程调用 sleep 方法主动放弃所占用的处理器资源
  • 线程在等待某个通知
  • 线程调用了一个阻塞式 IO 方法,在该方法返回之前,该线程被阻塞
  • 程序调用了线程的 suspend 方法将该线程挂起
  • 线程试图调用一个同步监视器,但该同步监视器正在被其他线程所占有
    当正在执行的线程被阻塞之后,其他线程就获得执行的机会了。被阻塞的线程会在合适的时候进入就绪状态

线程死亡

  • run() 方法执行完成,线程正常结束
  • 线程抛出一个未捕获的 Exception 或 Error
  • 直接调用该线程的 stop() 方法来结束该线程

当主线程结束后,其他线程不受影响

控制线程

join 线程

Thread 提供了让一个线程等待另一个线程完成的方法:join() 。当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到被 join 方法加入的 join 线程完成为止。

public class JoinThread extends Thread {
    public JoinThread(String name){
        //提供一个有参数的构造器,用于设置该线程的名字
        super(name);
    }
    //重写run方法
    public void run(){
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + " " + i);
        }
    }

    /*
    * 当主线程循环变量i等于20的时候,启动了名为“被join的线程”,该线程不会和main线程并发执行,
    * 而是main线程必须等到该线程执行结束后才开始向下执行。
    * 在名为“被join的线程”的线程执行时,实际上只有两条子线程并发执行,而主线程处于等待状态下
    * */
    public static void main(String[] args) throws Exception{
        //启动子线程
        new JoinThread("新线程").start();
        for (int i = 0; i < 100; i++){
            if(i == 20){
                JoinThread jt = new JoinThread("被join的线程");
                jt.start();
                //main线程调用了jt线程的join方法,main线程必须等jt线程结束才会向下执行
                jt.join();
                System.out.println(Thread.currentThread().getName() + " " + i);
            }
        }
    }
}

join 有三种重载的形式:

  • join():等待被 join() 的线程执行完成
  • join(long millis):等待被 join 的线程最长为 millis 毫秒
  • join(long millis, int nanos):等待被 join 的线程最长 millis 毫秒加上 nanos 微秒

后台线程

猜你喜欢

转载自blog.csdn.net/qq_32682177/article/details/83720534