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多线程设计模式

转载请注明出处,谢谢!

Java线程之线程简介的更多相关文章

  1. Java多线程系列——线程池简介

    什么是线程池? 为了避免系统频繁地创建和销毁线程,我们可以让创建的线程进行复用.用线程时从线程池中获取,用完以后不销毁线程,而是归还给线程池. JDK 对线程池的支持 为了更好的控制多线程,JDK 提 ...

  2. Java线程池中线程的状态简介

    首先明确一下线程在JVM中的各个状态(JavaCore文件中) 1.死锁,Deadlock(重点关注) 2.执行中,Runnable(重点关注) 3.等待资源,Waiting on condition ...

  3. java四种线程池简介,使用

    为什么使用线程池 1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务. 2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止消耗过多的内存 3.web项目应该创建统 ...

  4. (转)java自带线程池和队列详细讲解 - CSDN过天的专栏

    一简介 线程的使用在java中占有极其重要的地位,在jdk1.4极其之前的jdk版本中,关于线程池的使用是极其简陋的.在jdk1.5之后这一情况有了很大的改观.Jdk1.5之后加入了java.util ...

  5. java自带线程池和队列详细讲解

    Java线程池使用说明 一简介 线程的使用在java中占有极其重要的地位,在jdk1.4极其之前的jdk版本中,关于线程池的使用是极其简陋的.在jdk1.5之后这一情况有了很大的改观.Jdk1.5之后 ...

  6. 第9章 Java中的线程池 第10章 Exector框架

    与新建线程池相比线程池的优点 线程池的分类 ThreadPoolExector参数.执行过程.存储方式 阻塞队列 拒绝策略 10.1 Exector框架简介 10.1.1 Executor框架的两级调 ...

  7. Java多线程(六) —— 线程并发库之并发容器

    参考文献: http://www.blogjava.net/xylz/archive/2010/07/19/326527.html 一.ConcurrentMap API 从这一节开始正式进入并发容器 ...

  8. Java自带线程池和队列详解

    Java线程池使用说明 一简介 线程的使用在java中占有极其重要的地位,在jdk1.4极其之前的jdk版本中,关于线程池的使用是极其简陋的.在jdk1.5之后这一情况有了很大的改观.Jdk1.5之后 ...

  9. java自带线程池和队列详细讲解<转>

    Java线程池使用说明 一简介 线程的使用在java中占有极其重要的地位,在jdk1.4极其之前的jdk版本中,关于线程池的使用是极其简陋的.在jdk1.5之后这一情况有了很大的改观.Jdk1.5之后 ...

随机推荐

  1. 解决Windows10下安装Ubuntu16.04双系统后开机没有Ubuntu引导

    转载 https://blog.csdn.net/qq_27838307/article/details/79149791 1.按照网上教程在磁盘中压缩硬盘并且不需要给他新建卷标,就让他显示空闲就好了 ...

  2. Linux卸载MySql——ubuntu版

    卸载mysql 1)删除mysql的数据文件 sudo rm /var/lib/mysql/ -R 2)删除mqsql的配置文件 sudo rm /etc/mysql/ -R 3)自动卸载mysql的 ...

  3. python课堂整理21---初识装饰器

    一.装饰器: 本质就是函数,功能:为其他函数添加附加功能 原则: 1.不能修改被装饰函数的源代码 2.不能修改被修饰函数的调用方式 一个简单的装饰器 import time def timmer(fu ...

  4. git rebase 理解

    摘录自:https://blog.csdn.net/wangnan9279/article/details/79287631

  5. XSS危害——session劫持(转载)

    在跨站脚本攻击XSS中简单介绍了XSS的原理及一个利用XSS盗取存在cookie中用户名和密码的小例子,有些同学看了后会说这有什么大不了的,哪里有人会明文往cookie里存用户名和密码.今天我们就介绍 ...

  6. 2019.7 佳木斯培训A层

    day1题目及题解 day2题目及题解 day3题目及题解 day4题目及题解 day5题目及题解

  7. ubuntu中设置python默认版本

    看/usr/bin中的Python文件,发现该文件是python2.7的链接文件 于是直接删掉这个软链接,然后重新创建python2.6的链接文件: 1 rm /usr/bin/python 2 ln ...

  8. spring注解不支持静态变量注入

    spring注解不支持静态变量注入:今天敲代码  自动配置 配置: Animal.java package study01_autoconfig.beanConfig; import org.spri ...

  9. python创建虚拟环境(Windows)

    >>>构建Python虚拟环境的目的是为了防止真实环境被破坏!!! >>>每一个项目建议用一个虚拟环境为了防止软件版本号冲突!!! 1.在终端切换到一个新的磁盘 如 ...

  10. Wtm携手LayUI -- .netcore 开源生态我们是认真的!

    经过WTM团队和LayUI团队多次深入协商,双方于2019年7月29日在北京中国国际展览中心正式达成战略合作意向, 双方签署了战略合作框架协议,LayUI团队承诺使用WTM框架的任何项目都可以免费使用 ...