0%

Java多线程(一)-线程的创建

Java多线程(一)-线程的介绍和创建

进程和线程的区别

当一个程序进入内存运行,就变成一个进程,进程是系统进行资源分配和调度的最小单位,具有独立性,动态性和并发性。这里需要说明一点:并发性(concurrency)和并行性(parallel)两个概念,并发是指同一时间只能有一个指令被执行,多个进程指令被快速轮换执行;并行指多条执行同时在多个处理器上被执行。

多线程指一个进程同时并发处理多个任务,线程是进程的执行单元。进程被初始化后,主线程就被创建,同时还可以创建多个顺序执行流,这些执行流就是线程,线程之间相互独立。线程可以拥有堆栈,变量等,与其他线程共享该进程所有的资源,线程的调度管理由进程本身来完成。

线程的生命周期

线程有五种状态,新建,就绪,运行,阻塞,死亡五种。

新建和就绪

使用new创建一个线程后,线程处于新建状态,和对象一样由虚拟机分配内存,并初始化成员变量的值。当调用了start()方法后,线程处于就绪状态,虚拟机为其创建方法调用栈和程序计数器,然而线程并没有开始运行,何时运行则取决于JVM的线程调度器的调度。

如果不调用start()方法,而是直接执行run()方法,则其他线程无法并发执行,相当于执行了一个普通方法,而不是线程执行体。对于新建状态的线程,只能调用start()方法。

运行和阻塞

当一个线程获得CPU资源时,开始执行run()的线程执行体,它不可能一直处于运行状态,需要被中断让其它线程获得执行机会。线程的调度取决于底层平台所采用的策略,如抢占式策略,系统给每个线程一小段时间处理任务,之后会剥夺资源给其他线程,在选择线程时,系统会考虑线程的优先级。

当线程主动放弃了所占用的资源时,会进入阻塞状态,一般有如下情况:

  • 调用了sleep()主动放弃
  • 线程调用了阻塞式IO方法,该方法返回前被阻塞
  • 试图获得一个被其他线程拥有的同步监视器
  • 等待通知
  • 程序调用suspend()方法挂起线程(容易导致死锁)

被阻塞后需要重新进入就绪状态,等待线程调度器的调用。当发生以下情况线程可重新进入就绪状态:

  • sleep()方法结束
  • 阻塞式的IO方法已返回
  • 成功获得同步监视器
  • 等待通知时,收到其他线程的通知
  • 被挂起后调用了resume()方法恢复

线程进入阻塞后只能回到就绪状态,而就绪和运行状态的转换不受程序控制,由线程调度决定。

线程死亡

线程会以三种方式结束:

  • run方法或call方法执行完成
  • 线程抛出未捕获的异常或错误
  • 调用线程的stop()结束(会产生死锁)

注意,主线程结束即死亡后,其他线程不受影响,它们和主线程有同样地位。要测线程死没死,可以调用线程的isAlive()方法,当处于就绪运行,阻塞状态时,返回true;否则返回false,新建也不例外。当线程死亡后就回不来了,不能重新启动。

创建线程三种方法

线程对象必须是Thread类或其子类的实例。

通过继承Thread类创建
  1. 定义子类,重写run方法。run方法体就代表线程需要完成的任务,也叫线程执行体。
  2. 创建Thread子类的实例,即创建了线程对象。
  3. 调用对象的start()方法启动。

注意,程序至少创建一个主线程,其线程执行体不是run方法,而是main()方法。

有三个方法需要介绍:

  • Thread.currentThread():Thread类的静态方法,返回正在执行的线程对象
  • getName():Thread类的实例方法,可返回线程对象的名字。
  • setName(String name):设置线程的名字。
通过Runnable接口创建
  1. 定义Runnable接口的实现类,并重写该接口的run方法。
  2. 创建实现类的实例,以该实例来创建线程对象。
  3. 调用start()方法。
//MyThread为实现Runnable接口的类
MyThread t = new MyThread();
new Thread(t,"runnable实现的线程").start();

实现类的run方法仅作为线程执行体,线程对象则负责执行run方法。通过实现Runable接口,在run方法中获得线程对象,则必须使用Thread.currentThread()方法,而通过继承方法的线程类,直接使用this即可获得线程对象。

使用CallableFuture创建

Java5开始提供了一个Callable函数式接口,接口提供了一个call()方法作为线程执行体,它不像run方法,call方法有返回值,而且可以声明抛出异常。然而Callable接口不是Runnable的子接口,所以不能用来直接创建线程对象。

Future接口代表Callable接口里call方法的返回值,Java为Future接口提供了FutureTask实现类,该类实现了Future接口,还实现了Runnable接口,可以用来创建new线程对象,接口里有几个公共方法来控制它关联的Callable任务。比如V get()方法,返回Callable任务里call方法的返回值,该方法将导致阻塞,等线程结束才返回。

  1. 创建Callable实现类,并实现call方法,再创建实现类的实例,该call方法将作为线程执行体。
  2. 使用FutureTask类包装Callable对象。
  3. 使用FutureTask对象作为target,创建线程对象。
  4. 调用FutureTask对象的get方法获得子线程执行完后的返回值。

使用Lambda表达式直接创建Callable对象,无需先创建实现类,再创建Callable对象。

三种方式的对比

线程类继承了Thread类,不能再继承其他父类,并且不能共享实例变量,但访问当前线程简单;而通过接口的方式可以共享实例变量,还可以继承其他类,但是比较繁琐,访问当前线程对象需要使用Thread.currentThread()。以上就是线程的简单介绍和创建,希望有所帮助!