Android开发探索

Java多线程(一)基础知识

2017-11-07  本文已影响49人  闽越布衣

线程概述

线程与进程

进程

 每个运行中的任务(通常是程序)就是一个进程。当一个程序进入内存运行时,即变成了一个进程。每一个进程都有一定的独立功能,进程是系统进行资源分配与调度的一个独立单元。

线程

 线程是进程的执行单元,是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程可以拥有自己的堆栈,自己的程序计数器以及自己的局部变量,但不拥有系统资源,它与父进程的其它线程共享该进程的全部资源。
 一个线程可以创建和销毁另一个线程,同一个进程中的多个线程之间可以并发执行。
 线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。

线程实现

继承Thread类

  1. 定义Thread类的子类,并重写run()方法,该run()方法的方法体就代表了线程要完成的任务即线程的执行体;
  2. 创建Thread子类的实例,即创建线程对象;
  3. 调用线程对象的start()方法来启动该线程。(注意,不是直接调用对象的run()方法,调用对象的run()方法,其实就相当于普通的方法调用,并不会创建线程);


    通过继承Thread类实现线程.png
    线程执行结果.png

     从图中可以看到,程序创建了三个线程,包含一个主线程和两个子线程。并且可以看到,线程输出并不是连续的,这是因为线程的执行是基于系统资源调度执行的。
     从图中可以看出,sum变量不是连续的。使用继承Thread的方法来创建线程类时,多个线程之间无法共享线程类的实例变量;因为每次创建线程对象时都是需要创建一个MyThread对象,每个对象都包含自己的实例变量。

实现Runnable接口

  1. 定义实现Runnable接口的实现类,并重写接口的run()方法,该run()方法的方法体就代表了线程要完成的任务即线程的执行体;
  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象;
  3. 调用Thread对象的start()方法来启动该线程。(注意,不是直接调用Runnable对象的run()方法,调用Runnable对象的run()方法,其实就相当于普通的方法调用,并不会创建线程);


    通过实现Runnable接口实现线程.png
    线程执行结果.png

     从图中可以看出,sum变量时连续的。使用实现Runnable的方法来创建线程类时,多个线程之间可以共享线程类的实例变量;因为在这种方式下,程序所创建的Runnable对象只是线程的target,而多个线程可以共享同一个target,所以多个线程可以共享同一个线程类(线程的target类)的实例变量。

通过Callable和Future创建线程

  1. 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值;
  2. 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值;
  3. 使用FutureTask对象作为Thread对象的target创建并启动新线程;
  4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值,注意不是直接调用Callable对象的call()方法获取返回值,Callable对象的call()方法为线程的执行体被调用;


    通过Callable和Futrue创建线程.png
    线程执行结果.png

     从图中可以看到当主线程的sum变量循环到20时,程序启动以FutureTask对象为target的线程,然后通过调用FutureTask对象的get()方法来获得call()方法的返回值。该方法将导致程序的主进程被阻塞,直到call()方法结束并返回为止。

Callable 接口方法

Future接口方法

创建线程三种方式的比较

采用继承Thread类创建线程的优缺点

采用实现Runnable,Callable接口创建线程的优缺点

线程状态

新建状态(NEW)

 当用new关键字创建一个新线程时,该线程处于新建状态。此时他和其他的Java对象一样,仅仅由JVM为其分配内存,并初始化成员变量的值,此时线程对象没有表现出线程的动态特征,程序也不会执行线程的执行体。

可运行状态(RUNNABLE)

 当线程对象调用了start()方法后,线程处于runnable状态,JVM为其创建方法调用栈和程序计数器。处于runnable状态的线程,可能正在运行也可能没有运行,这取决于JVM里线程调度器的调度,当线程获得CPU时间片时,线程执行。
 当一个线程开始运行时,它不可能处于一直运行的状态(除非它的线程执行体足够短,瞬间就执行结束了)。线程在运行过程中需要被中断,目的是让其他线程获得执行的机会,线程调度的细节依赖于操作系统提供的服务。

阻塞与(计时)等待状态(BLOCKED,WAITING,TIMED_WAITING)

 当线程处于阻塞或者等待状态时,它暂时不活动。直到线程调度器重新激活它。细节取决于它是怎么达到非活动状态的。

终止状态(TERMINATED)

 线程因如下原因之一而被终止:(当然还有一种调用stop()方法,不过该方法已过时,不建议调用)

Thread 方法

线程属性

线程优先级

 每个线程执行都有一定的优先级,优先级高的获得较多的执行机会,优先级低的执行机会先对较少。默认情况下,一个线程继承它的父线程优先级;默认情况下,main线程具有普通优先级,由main创建的线程也具普通优先级。
 Thread类提供setPriority(int newPriority),getPriority()方法来设置和获取指定线程的优先级。可以将优先级设置为MIN_PRIORITY(Thread类中定义为1)与MAX_PRIORITY(定义为10)之间的任何值。默认NORM_PRIORITY被定义5。
 每当线程调度器有机会选择新线程时,他首先选择具有高优先级的线程。但是线程优先级是高度依赖于操作系统的。不同操作系统上的优先级并不相同,而且也不能很好的和Java的10个优先级对应。(例如Windows有7个优先级。一些Java优先级将映射到相同的操作系统优先级。在Oracle为Linux提供的Java虚拟机中,线程的优先级被忽略——所有的线程具有相同的优先级。(来自Java核心技术 卷I))。所以应尽量避免直接为线程指定优先级。

Thread 方法

守护线程

 可以通过调用setDaemon(true)将线程转换为守护线程。守护线程的唯一用途就是为其它线程提供服务。当只剩下守护线程时虚拟机就退出了,由于如果只剩下守护线程,就没必要继续运行程序了。
 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。

Thread 方法

常用方法

线程面试

什么是线程?

 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。

线程与进程的区别?

 进程是一个独立的运行环境,它可以被看作是一个程序或者一个应用。而线程是在进程中执行的一个任务。线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。别把它和栈内存搞混,每个线程都拥有单独的栈内存用来存储本地数据。

如何在Java中实现线程?

Thread 类中的start() 和 run() 方法有什么区别?

 start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。

Runnable和Callable有什么不同?

 Runnable和Callable都代表那些要在不同的线程中执行的任务。Runnable从JDK1.0开始就有了,Callable是在JDK1.5增加的。它们的主要区别是Callable的 call() 方法可以返回值和抛出异常,而Runnable的run()方法没有这些功能。Callable可以返回装载有计算结果的Future对象。

Thread类的sleep()方法和对象的wait()方法都可以让线程暂停执行,它们有什么区别?

 sleep()方法是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指定的时间,将执行机会(CPU)让给其他线程,但是对象的锁依然保持,因此休眠时间结束后会自动恢复(线程回到就绪状态)。wait()是Object类的方法,调用对象的wait()方法导致当前线程放弃对象的锁(线程暂停执行),进入对象的等待池,只有调用对象的notify()方法(或notifyAll()方法)时才能唤醒等待池中的线程进入等锁池,如果线程重新获得对象的锁就可以进入就绪状态。

线程的sleep()方法和yield()方法有什么区别?


    整理文章主要为了自己日后复习用,文章中可能会引用到别的博主的文章内容,如涉及到博主的版权问题,请博主联系我。

上一篇 下一篇

猜你喜欢

热点阅读