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. Spring + RocketMQ使用

    本文所介绍环境为win7环境下运行, 从官方github中(https://github.com/alibaba/RocketMQ)下载RocketMQ-master.zip,版本为v3.5.8,解压 ...

  2. android 基于wifi模块通信开发

    这篇文章主要是我写完手机与wifi模块通信后所用来总结编写过程的文章,下面,我分几点来说一下编写的大概流程. 一.拉出按钮控件并设置它的点击事件 二.设置wifi权限 三.打开和关闭wifi 四.扫描 ...

  3. jboss 未授权访问漏洞复现

    jboss 未授权访问漏洞复现 一.漏洞描述 未授权访问管理控制台,通过该漏洞,可以后台管理服务,可以通过脚本命令执行系统命令,如反弹shell,wget写webshell文件. 二.漏洞环境搭建及复 ...

  4. Java的编译原理

    概述 java语言的"编译期"分为前端编译和后端编译两个阶段.前端编译是指把*.java文件转变成*.class文件的过程; 后端编译(JIT, Just In Time Comp ...

  5. spring boot 学习笔记之前言----环境搭建(如何用Eclipse配置Maven和Spring Boot)

    本篇文档来源:https://blog.csdn.net/a565649077/article/details/81042742 1.1 Eclipse准备 (1)     服务器上安装JDK和Mav ...

  6. Android PDA扫描枪广播接搜条码并使用

    在开发扫描枪扫码接收广播条码的时候,由于厂商如shit般的文档和对Anroid基础知识的缺失,走了一些弯路,以下是广播接收条码并使用的代码实现 : 1 : 动态注册广播 PDA扫描枪对扫码有强大支持, ...

  7. 数据结构之最小堆的实现C++版

    完全二叉树之所以用数组的方式存在,在于他的一个特性 若子节点为i,则父节点为(i-1)/2,注意c++特性,该结果肯定是个整数. 若父节点为j,则子节点必为2*j+1;则在数组里面可以非常方便的通过下 ...

  8. 数据结构之队列C++版

    #include "stdafx.h"/* 队列是一种先进先出的线性表队列的核心是对头部和尾部索引的操作 如上图所示,当对头索引移动到最前面6,队尾又不不再末尾0的位置,那么如果不 ...

  9. git常用指令整理

    git常用指令一览表 GIT指令 说明 git add . 将全部文件的内容加到Git索引以便执行commit. 这个指令不会检查文件夹中是否有文件被删除. 要注意的是,只有执行" git ...

  10. 关于Linux的简单介绍

    Linux: 诞生日期:1991年 开发者:林纳斯·托瓦茨 特点:免费,开源    发行版本:centos|red Hat|Ubuntu|红旗等    思想:一切都是文件 重要文件目录 bin:二进制 ...