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. MyEclipse 2016 Stable 1.0破解教程

    一.下载所需文件 1. Windows最新版: MyEclipse 2016 Stable 1.0离线安装包(文件大小:1.52GB)--完整安装包,无需在线下载http://pan.baidu.co ...

  2. Linux更换默认Security源

    很多时候 修改了软件源,但是发现更新还是很慢,查看一下,如下图,有一个security ,显然主源还是在ubuntu,ubuntu本身在国外,所以很慢,因此考虑替换为国内镜像. 图1 1.备份数据源列 ...

  3. nginx文件名逻辑漏洞_CVE-2013-4547漏洞复现

    nginx文件名逻辑漏洞_CVE-2013-4547漏洞复现 一.漏洞描述 这个漏洞其实和代码执行没有太大的关系,主要原因是错误地解析了请求的URL,错误地获取到用户请求的文件名,导致出现权限绕过.代 ...

  4. visionpro和halcon这两款机器视觉软件区别

    很多朋友会问到visionpro和halcon这两款机器视觉软件,到底学哪个好呢,今天重码网就给大家讲一讲: 首先比较下两者的优缺点: halcon: 提供的图像算法要比Visionpro多,也就是说 ...

  5. VM虚拟机Linux系统eth0下面没有inet和inet6

    今天打开虚拟机发现ip有问题,VM虚拟机Linux系统eth0下面没有inet和inet6,明明都是配置好的 打开任务管理器-> 服务-> 打开VM的nat和DHCP和hostd 正常后:

  6. css清除select默认的样式

    select在firefox与chrome的显示是不一样的,我们一般选择通过css清除掉css的默认样式,然后再增添自定义的样式来解决,css我们一般用这么几行代码来清除默认样式: 1 select ...

  7. 使用vsftp与shell实现对进程与服务状态的监控

    先说一下需求吧,公司开发了一款新的产品,新产品嘛,有着不得不出问题的理由,四个云机房,总共三百余台机器,需要实时的监控进程状态,虽然有zabbix来实现,但领导需求是脚本和zabbix一起做,zabb ...

  8. Qtech 暑假未讲到的算法(不完全)

    一.数据结构:    优先队列.堆.RMQ问题(区间最值问题,可以用线段树解决,还有一个Sparse-Table算法).排序二叉树.划分树.归并树.....   字符串处理:    KMP.字典树.后 ...

  9. Ubuntu+VMWare 学习中遇到的问题

    1. 虚拟机中Ubuntu分辨率 / 设置分辨率出现Unknown Display VMware中Ubuntu 出现Unknown Display问题解决 1.1 命令无法保存分辨率设置: xrand ...

  10. 注解与AOP切面编程实现redis缓存与数据库查询的解耦

    一般缓存与数据库的配合使用是这样的. 1.查询缓存中是否有数据. 2.缓存中无数据,查询数据库. 3.把数据库数据插入到缓存中. 其实我们发现 1,3 都是固定的套路,只有2 是真正的业务代码.我们可 ...