Java线程之线程简介

一、何谓线程

  • 明为跟踪处理流程,实为跟踪线程

  阅读程序时,我们会按处理流程来阅读。

  首先执行这条语句

     ↓

  然后执行这条语句

     ↓

  接着再执行这条语句……

  我们就是按照上面这样的流程阅读程序的。

  如果将程序打印出来,试着用笔将执行顺序描画出来,就会发现最终描画出来的是一条弯弯曲曲的长线。

  这条长线始终都会是一条。无论是调用方法,还是执行for 循环、if 条件分支语句,甚至更复杂的处理,都不会对这条长线产生影响。对于这种处理流程始终如一条线的程序,我们称之为单线程程序(single threaded program)。

  在单线程程序中,“在某一时间点执行的处理”只有一个。如果有人问起“程序的哪部分正在执行”,我们能够指着程序中的某一处回答说“这里,就是这儿”。这是因为,在单线程程序中,“正在执行程序的主体”只有一个。

  线程对应的英文单词Thread 的本意就是“线”。Java 语言中将此处所说的“正在执行程序的主体”称为线程A。我们在 阅读程序时,表面看来是在跟踪程序的处理流程,实际上跟踪的是线程的执行。

二、单线程程序

  这里我们先来执行一个简单的单线程程序。如下是一个显示10 000 次Good! 字符串的单线程程序。

  单线程程序(Main.java)

  public class Main {
    public static void main(String[] args) {

      for (int i = 0; i < 1000; i++) {

        System.out.print("Good!");

      }

    }

  }

  如果你使用的是Java Development Kit(JDK),请在命令行输入如下内容。

  javac Main.java

  接下来,javac 命令便会编译源文件Main.java,并生成类文件Main.class。

  然后,在命令行再输入如下内容。

  java Main

  接下来,java 命令便会执行该程序,在屏幕上显示10 000 个Good!

  Java 程序执行时,至少会有一个线程在运行。代码清单I1-1 中运行的是被称为主线程(mainthread)的线程,执行的操作是显示字符串。

  在命令行输入如下内容,主线程便会在Java 运行环境中启动。

  java 类名

  然后,主线程会执行命令行中输入的类的main 方法。main 方法中的所有处理都执行完后,主线程也就终止了,如下图。

  上述代码中只有一个线程在运行,所以这是一个单线程程序。

  • 后台运行的线程

  为了便于说明,前面的讲解说的是“只有一个线程在运行”。其实严格来讲,Java 处理的后台也有线程在运行。例如垃圾回收线程、GUI 相关线程等。

三、多线程程序

  由多个线程组成的程序就称为多线程程序(multithreaded program)。Java 编程语言从一开始就把多线程处理列入编程规范了。

  多个线程运行时,如果跟踪各个线程的运行轨迹,会发现其轨迹就像多条线交织在一起。

  假设有人问起“程序的哪部分正在执行”,而我们需要指出程序位置,并回答“这里,就是这儿”。那么在多线程的情况下,一根手指根本不够用,这时需要和线程个数一样多的手指。也就是说,如果有两个线程在运行,那就需要指出两个地方并回答“第一个线程正在这里执行,第二个线程在那里执行”;如果有三个线程,就要指出三个地方;如果有一百个线程,就要指出一百个地方。

  当规模大到一定程度时,应用程序中便会自然而然地出现某种形式的多线程。以下便是几种常见示例。

  ◆◆GUI 应用程序

  几乎所有的GUI 应用程序中都存在多线程处理。例如,假设用户在使用文本工具编辑较大的文本文件时执行了文字查找操作。那么当文本工具在执行查找时,屏幕上会出现“停止查找”按钮,用户可随时停止查找。此时就需要用到多线程。

  (1)执行查找

  (2)显示按钮,并在按钮被按下时停止查找

  这两个操作是分别交给不同的线程来执行的。这样一来,(1)的操作线程专门执行查找,而(2)的操作线程则专门执行GUI 操作,因此程序就会比较简单。

  ◆◆耗时的I/O 处理

  一般来说,文件与网络的I/O 处理都非常消耗时间。如果在I/O 处理期间,程序基本上无法执行其他处理,那么性能将会下降。在这种情况下,就可以使用多线程来解决。如果将执行I/O 处理的线程和执行其他处理的线程分开,那么在I/O 处理期间,其他处理也可以同时执行。

  ◆◆多个客户端

  基本上,网络服务器都需要同时处理多个客户端。但是,如果让服务器端针对多个客户端执行处理,那么程序会变得异常复杂。这种情况下,在客户端连接到服务器时,我们会为该客户端准备一个线程。这样一来,服务器程序就被设计成了好像只处理一个客户端。具体示例将在第7 章的习题7-6 中再进行介绍。

  兼具性能和可扩展性的I/O 处理

  java.nio 包中包含兼具性能和可扩展性的I/O 处理。有了这个包,即便不使用线程,也可以执行兼具性能和可扩展性的I/O 处理。具体内容请参见API 文档。

四、Thread 类的run 方法和start 方法

  接下来,我们试着编写一个多线程程序。Java 程序运行时,最开始运行的只能是主线程。所以,必须在程序中启动新线程,这才能算是多线程程序。启动线程时,要使用如下类(一般称为Thread 类)。

  java.lang.Thread

  我们让MyThread类继承Thread类,Thread类实现了Runnable接口,Runnable接口声明了run方法,这里重写了Thead类继承下来的run方法

  public class MyThread extends Thread {
      @Override
      public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("Nice!");
        }
      }
  }

  该方法执行的处理是输出10 000 次Nice! 字符串。

  新启动的线程的操作都编写在run 方法中(run 就是“跑”的意思)。新线程启动后,会调用run 方法。随后,当run 方法执行结束时,线程也会跟着终止。MyThread 类中的run 方法写得没有问题,但如果仅是这样,程序什么操作也不会做,所以必须新启动一个线程,调用run 方法才可以。

  用于启动线程的代码如下。创建一个MyThread 的实例,并利用该实例启动新的线程。然后,程序会再执行自身(主线程)的任务,输出10 000 次Good!。主线程主要执行如下两个任务。

● 启动输出Nice!操作的新线程

● 输出Good!

  用于启动新线程的程序(Main.java)

  public class Main {
    public static void main(String[] args) {
      MyThread thread = new MyThread();
      thread.start();
      for (int i = 0; i < 1000; i++) {
        System.out.println("Good!");
      }
    }
  }

  我们来看一下这几行代码,通过下面这行语句,主线程会创建MyThread 类的实例,并将其赋给变量thread。

  MyThread thread = new MyThread();

  下面这行语句则是由主线程启动新线程。

  thread.start();

  start 方法是Thread 类中的方法,用于启动新的线程。

  在此需要注意的是,启动新线程时调用的是start 方法,而不是run 方法。当然run 方法是可以调用的,但调用它并不会启动新的线程。

  调用start 方法后,程序会在后台启动新的线程。然后,由这个新线程调用run 方法。

  start 方法主要执行以下操作。

  ● 启动新线程

  ● 调用run方法

  start 方法与run 方法之间的关系如下图所示。图中出现了两条线(即图中的灰线)。

  从输出结果我们可以发现Good! 字符串和Nice! 字符串是交织在一起输出的。由于这两个线程是并发运行的,所以结果会像图中这样混在一起。这两个线程负责的操作如下。

  ● 主线程输出Good!字符串

  ● 新启动的线程输出Nice!字符串

  以上的程序中运行着两个线程,所以这是一个多线程程序。

  下面简单说明一下顺序、并行与并发这三个概念。

  • 顺序(sequential)用于表示多个操作“依次处理”。比如把十个操作交给一个人处理时,这个人要一个一个地按顺序来处理。
  • 并行(parallel)用于表示多个操作“同时处理”。比如十个操作分给两个人处理时,这两个人就会并行来处理。
  • 并发(concurrent)相对于顺序和并行来说比较抽象,用于表示“将一个操作分割成多个部分并且允许无序处理”。 比如将十个操作分成相对独立的两类,这样便能够开始并发处理了。如果一个人来处理,这个人就是顺序处理分开的并发操作,而如果是两个人,这两个人就可以并行处理同一个操作。

  如果CPU 只有一个,那么并发处理就是顺序执行的,而如果有多个CPU,那么并发处理就可能会并行运行。

  我们使用的计算机通常情况下只有一个CPU,所以即便多个线程同时运行,并发处理也只能顺序执行。比如“输出Good! 字符串的线程”和“输出Nice! 字符串的线程”这两个线程就是像下面这样运行的。

  ● 输出Good!字符串的线程稍微运行一下后就停止

            ↓

  ● 输出Nice!字符串的线程稍微运行一下后就停止

            ↓

  ● 输出Good!字符串的线程稍微运行一下后就停止

            ↓

  ● 输出Nice!字符串的线程……

  实际上运行的线程就像上面这样在不断切换,顺序执行并发处理。

  多线程编程时,即使能够并行执行,也必须确保程序能够完全正确地运行。也就是说,必须正确编写线程的互斥处理和同步处理。

  并发处理的顺序执行与并发处理的并行执行示意图如下图所示。

参考:图解Java多线程设计模式

转载请注明出处,谢谢!

最新文章

  1. Struts框架的核心业务
  2. ajax之 get post请求
  3. Android Listview
  4. absolute和fixed
  5. UITableView系列(1)---Apple缓存池机制
  6. 【一天一道LeetCode】#22. Generate Parentheses
  7. OpenCV 闭合轮廓检测
  8. vue报错Error in render: &quot;TypeError: Cannot read property &#39;0&#39; of undefined&quot;
  9. linux 一键安装lnmp环境
  10. 深入理解ASP.NET MVC(6)
  11. Java NIO系列教程(三) Channel之Socket通道
  12. flutter 调用原生(获取当前设备电池电量)
  13. PAT B1027 打印沙漏 (20 分)
  14. Iocomp控件教程之Analog Display—模拟显示控件(优于EDIT控件)
  15. javascript对数据处理
  16. 浅谈FIle协议与Http协议及区别
  17. datanode启动失败
  18. 201671010140. 2016-2017-2 《Java程序设计》java学习第十一周
  19. Android 自定义ListView实现底部分页刷新与顶部下拉刷新,androidlistview
  20. Joiner的用法

热门文章

  1. 基于ZK的 Dubbo-admin 与 Dubbo-monitor 搭建
  2. linux初学者-系统服务的控制
  3. vue教程二 vue组件(3)
  4. Shell.Users 提权
  5. JAVA开始(基础篇)
  6. c#异常后重试操作
  7. JavaFX 选择文件 导入Excel文件并解析
  8. 前端笔记之React(八)上传&amp;图片裁切
  9. 【React踩坑记三】React项目报错Can&#39;t perform a React state update on an unmounted component
  10. Jquery 实现添加删除,checkbok 的全选,反全选,但是批量删除没有实现