第15章多线程编程 编译结束后,就会生成相应的类文件Execution.class。.Execution类是有main(方法的, 所以可以使用java命令来执行这个类文件。在使用如下命令执行Execution类的时候, java com.javaeasy.execution.Execution 首先Java平台会启动,启动完毕后,Java平台会创建一个线程。然后Java平台会读 取给它的参数,也就是com.javaeasy.execution.Execution。它是类的全限定名,Java平台会 根据这个名字来寻找需要执行的类,找到Execution.class文件后,就会把它读取并加载到 程序中,然后让开始创建出来的线程去执行Execution类中的mainO方法。 关于mian(0方法:main(O方法从语法上来说只是一个普通的方法,它的特殊之处在于 “Java平台会将mainO方法作为一个程序的开始,让线程从这里开始执行程序”。这也是为 什么所有可以直接被执行的Java程序都必须有这么一个mainO方法的原因。main(O方法是 一个约定俗成,无论Java平台以什么方法作为程序的开始都是没有关系的。 例程中的第1行是一个int变量的加法运算和it变量的赋值操作。线程在执行的时候 就会为变量分配内存并进行运算。例程中的第2行是一个方法调用。对于方法调用,线程 执行的时候会将这个方法“展开”(也就是进去被调用方法内部。在第14章讲解异常传递 时,就在方法调用的时候将方法展开了),这样一个方法调用就变成了执行方法中的代码 了。线程就会这样逐行代码地执行下去,直到main(方法结束。 口说明:线程始终都是在一行行地执行代码,遇到方法调用时,就进去被调用方法内部去 一行行执行。当然,我们在阅读程序的时候,可以简单地认为方法调用就是一个 运算。只有在必要的时候,我们才将一个方法调用“展开”。 通过上面的分析,线程这个概念己经了解的差不多了,后面将讲述线程编程的基础。 本节的内容是后面内容的基础。 口线程的作用就是从某个指定的方法(如main0方法)开始逐行执行代码。就好像 演奏家按照乐谱一小节一小节演奏一样。 口执行完指定的方法线程就结束了。 口通过演奏会模型加深对线程的理解。 15.2Java中的线程编程 前面对“线程是什么”做了大量的讲述和类比。本节中将讲述如何使用线程编程。通 过前面的内容我们知道,线程的作用就是“执行一个方法,执行完这个方法后,线程就结 束了”。在Java中我们可以自由地使用线程:创建线程,指定它需要执行的方法,然后启 动线程,这个线程就会执行下去了。请看本节的内容。 15.2.1线程类Thread 在Java语言中,线程被封装为Thread类(全限定名是java.lang.Thread)。当然,线 程核心的内容并不在这个类中,因为真正的线程是存在于Java平台中的。可以把这个类认 ·443·
第 15 章 多线程编程 ·443· 编译结束后,就会生成相应的类文件 Execution.class。Execution 类是有 main()方法的, 所以可以使用 java 命令来执行这个类文件。在使用如下命令执行 Execution 类的时候, java com.javaeasy.execution.Execution 首先 Java 平台会启动,启动完毕后,Java 平台会创建一个线程。然后 Java 平台会读 取给它的参数,也就是 com.javaeasy.execution.Execution。它是类的全限定名,Java 平台会 根据这个名字来寻找需要执行的类,找到 Execution.class 文件后,就会把它读取并加载到 程序中,然后让开始创建出来的线程去执行 Execution 类中的 main()方法。 关于 mian()方法:main()方法从语法上来说只是一个普通的方法,它的特殊之处在于 “Java 平台会将 main()方法作为一个程序的开始,让线程从这里开始执行程序”。这也是为 什么所有可以直接被执行的 Java 程序都必须有这么一个 main()方法的原因。main()方法是 一个约定俗成,无论 Java 平台以什么方法作为程序的开始都是没有关系的。 例程中的第 1 行是一个 int 变量的加法运算和 int 变量的赋值操作。线程在执行的时候 就会为变量分配内存并进行运算。例程中的第 2 行是一个方法调用。对于方法调用,线程 执行的时候会将这个方法“展开”(也就是进去被调用方法内部。在第 14 章讲解异常传递 时,就在方法调用的时候将方法展开了),这样一个方法调用就变成了执行方法中的代码 了。线程就会这样逐行代码地执行下去,直到 main()方法结束。 说明:线程始终都是在一行行地执行代码,遇到方法调用时,就进去被调用方法内部去 一行行执行。当然,我们在阅读程序的时候,可以简单地认为方法调用就是一个 运算。只有在必要的时候,我们才将一个方法调用“展开”。 通过上面的分析,线程这个概念已经了解的差不多了,后面将讲述线程编程的基础。 本节的内容是后面内容的基础。 线程的作用就是从某个指定的方法(如 main()方法)开始逐行执行代码。就好像 演奏家按照乐谱一小节一小节演奏一样。 执行完指定的方法线程就结束了。 通过演奏会模型加深对线程的理解。 15.2 Java 中的线程编程 前面对“线程是什么”做了大量的讲述和类比。本节中将讲述如何使用线程编程。通 过前面的内容我们知道,线程的作用就是“执行一个方法,执行完这个方法后,线程就结 束了”。在 Java 中我们可以自由地使用线程:创建线程,指定它需要执行的方法,然后启 动线程,这个线程就会执行下去了。请看本节的内容。 15.2.1 线程类 Thread 在 Java 语言中,线程被封装为 Thread 类(全限定名是 java.lang.Thread)。当然,线 程核心的内容并不在这个类中,因为真正的线程是存在于 Java 平台中的。可以把这个类认
第2篇Java语言高级语法 为是真正的线程的“代理人”。我们操作Thread类时,Thread类就会操作真正的线程,这 就好像“使用引用操作引用指向的对象”。 在程序中,可以按照需要创建和使用线程。当创建一个Thread类的实例时,在Java 平台内部,一个真正的线程同时被创建了出来。其实使用线程很简单,根据前面对线程的 介绍,使用线程的时候只需要关心下面两点就可以了。 口如何指定线程需要执行的方法。我们知道,线程的作用就是执行一个方法,直到 方法结束,线程也完成了使命。 口如何启动一个线程。当创建出了线程,也指定了线程需要执行的方法,下面的事 情就是“推动一下”,让线程启动起来。 下面就来讲述使用Java线程的第一种办法。 15.2.2覆盖Thread类的run0方法 为了使用线程,首先需要学习Thread类中的两个重要方法。 口Thread():这是Thread类的一个构造方法,它没有任何参数,所以说创建线程的实 例也是很简单的,可以不提供任何参数。 口void start():start()方法就是启动线程的方法,这个方法是线程类中最核心的方法。 当调用这个方法以后,它就会启动线程,并让线程去执行指定的方法,而这里说 的“指定的方法”,就是下面要说的run(O方法。 口void run():runO方法是Thread类中一个普通的方法,它的特殊之处仅仅在于start0( 方法会将它作为线程的起点。 好,知道了Thread类中这两个方法的作用后,如何让runO方法变成想让线程去执行 的方法呢?这里就要用到继承和覆盖了。我们使用一个类去继承Thread类,然后为这个 Thread类的子类添加一个runO方法,用来覆盖Thread类中原来的runO方法。那么,根据 Java覆盖的原则,start()方法再调runO方法的时候,其实就是调用的Thread类中子类的run) 方法了。也就是说,“只要在Thread类的子类中的run(O方法内部,编写需要让线程执行 的代码”就可以了。 下面以一个例子来演示这种使用线程的方法,首先创建一个Thread类的子类,并覆盖 Thread类中的runO方法。 package com.javaeasy.usethread; //程序所在的包 public class MyThread extends Thread /MyThread类继承自Thread类 public void run(){ /覆盖Thread类中的run()方法 System.out,println("这是在另一个线程中执行的代码。"): /向控制台输出一行字 //run()方法结束 上面的类很简单.首先需要注意的就是MyThread类继承自Thread类,然后是MyThread 类覆盖了Thread类中的runO0方法。这样才能够让线程在启动后(调用start()方法后)执行 到想让线程执行的内容。好,下面是一个使用MyThread类的例程。 ·444·
第 2 篇 Java 语言高级语法 ·444· 为是真正的线程的“代理人”。我们操作 Thread 类时,Thread 类就会操作真正的线程,这 就好像“使用引用操作引用指向的对象”。 在程序中,可以按照需要创建和使用线程。当创建一个 Thread 类的实例时,在 Java 平台内部,一个真正的线程同时被创建了出来。其实使用线程很简单,根据前面对线程的 介绍,使用线程的时候只需要关心下面两点就可以了。 如何指定线程需要执行的方法。我们知道,线程的作用就是执行一个方法,直到 方法结束,线程也完成了使命。 如何启动一个线程。当创建出了线程,也指定了线程需要执行的方法,下面的事 情就是“推动一下”,让线程启动起来。 下面就来讲述使用 Java 线程的第一种办法。 15.2.2 覆盖 Thread 类的 run()方法 为了使用线程,首先需要学习 Thread 类中的两个重要方法。 Thread():这是 Thread 类的一个构造方法,它没有任何参数,所以说创建线程的实 例也是很简单的,可以不提供任何参数。 void start():start()方法就是启动线程的方法,这个方法是线程类中最核心的方法。 当调用这个方法以后,它就会启动线程,并让线程去执行指定的方法,而这里说 的“指定的方法”,就是下面要说的 run()方法。 void run():run()方法是 Thread 类中一个普通的方法,它的特殊之处仅仅在于 start() 方法会将它作为线程的起点。 好,知道了 Thread 类中这两个方法的作用后,如何让 run()方法变成想让线程去执行 的方法呢?这里就要用到继承和覆盖了。我们使用一个类去继承 Thread 类,然后为这个 Thread 类的子类添加一个 run()方法,用来覆盖 Thread 类中原来的 run()方法。那么,根据 Java 覆盖的原则,start()方法再调 run()方法的时候,其实就是调用的 Thread 类中子类的 run() 方法了。也就是说,“只要在 Thread 类的子类中的 run()方法内部,编写需要让线程执行 的代码”就可以了。 下面以一个例子来演示这种使用线程的方法,首先创建一个 Thread 类的子类,并覆盖 Thread 类中的 run()方法。 package com.javaeasy.usethread; // 程序所在的包 public class MyThread extends Thread { // MyThread 类继承自 Thread 类 public void run() { // 覆盖 Thread 类中的 run()方法 System.out.println("这是在另一个线程中执行的代码。"); // 向控制台输出一行字 } // run()方法结束 } 上面的类很简单。首先需要注意的就是MyThread类继承自Thread类,然后是MyThread 类覆盖了 Thread 类中的 run()方法。这样才能够让线程在启动后(调用 start()方法后)执行 到想让线程执行的内容。好,下面是一个使用 MyThread 类的例程
第15章多线程编程 package com.javaeasy.usethread; /1程序所在的包 public class UseMyThread //例程类 public static void main(String[]args){ //main()方法 MyThread thread new MyThread(); //创建一个Thread类的实例 thread.start(); //启动一个新的线程 运行上面的例程,控制台输出如下内容: 这是在另一个线程中执行的代码。 到这里,线程似乎还没有带来什么让人兴奋的特征。不过不着急,下面首先在图15-6 中使用“演奏家模型”说明一下上面例程的执行过程(省略关于编译等无关的步骤)。 Java平台创建一个线程(线程I) 指挥家请出一位演奏家(演奏家1) Java平台让加JseMyThread类 指挥家搬出一台显示器 并找到其中的mainO方法 并让显示器显示需要演奏的乐谱 Java平台让线程1执行 指挥家让演奏家按照 UseMyThread的main()方法 显示器上的乐谱进行演奏 mainO方法第一行,创建一个 演奏家发现乐谱上需要另一位演奏 家,于是让指挥家请出另一位演奏 新的线程的实例(线程2) 家(演奏家2)和新的显示器 main(O方法第二行 Java平台启动线程2 演奏家1告诉指挥家可以 演奏家2看到指挥家做出 启动这个线程 让演奏家2开始演奏了 手势,开始演奏乐谱 执行main()方法的 演奏家1的乐谱结束, 线程结束 线程2执行rmO方法第一 演奏家1退场 演奏家2演奏其 行,向控制台输出相关内容 显示器上的乐谱 run0方法结東,线程2也结束 演奏家2的乐谱结束 演奏家2退场 所有线程都结束了,Java平台退出 演奏会演奏结束指挥家退场 图15-6创建并启动线程的程序执行过程 图15-6可以看出如下几点内容。 ·445·
第 15 章 多线程编程 ·445· package com.javaeasy.usethread; // 程序所在的包 public class UseMyThread { // 例程类 public static void main(String[] args) { // main()方法 MyThread thread = new MyThread(); // 创建一个 Thread 类的实例 thread.start(); // 启动一个新的线程 } } 运行上面的例程,控制台输出如下内容: 这是在另一个线程中执行的代码。 到这里,线程似乎还没有带来什么让人兴奋的特征。不过不着急,下面首先在图 15-6 中使用“演奏家模型”说明一下上面例程的执行过程(省略关于编译等无关的步骤)。 图 15-6 创建并启动线程的程序执行过程 图 15-6 可以看出如下几点内容
第2篇Java语言高级语法 口新线程的创建和启动其实都是通过Java代码触发的。实际上,除了第一个线程(也 就是启动程序的、运行mainO方法的线程)是由Java平台直接创建的之外,其余 的线程都是在Java代码中通过“创建Thread类的实例,启动线程”这种方式创建 并启动的。 口当启动一个新的线程时,其过程是:由Java代码通知Java平台,Java平台再启动 线程。例如在图15-6中,线程1启动线程2的过程实际上就是:线程1执行 thread.start(),这个方法在内部会让Java平台启动第二个线程。所以,启动线程2 对于线程1来说,是一个很短的过程,因为启动线程的具体事情都是Java平台做 的,线程1只是“通知”Java平台要启动线程2而已,通知完了就继续执行代码, 不等待线程2。 口只有所有的线程都退出以后,程序才会退出。 如果这个过程看上去太抽象,请对比着右边演奏会的过程进行理解。下面学习另一种 使用Java线程的方式。 口Thread类的start)(方法是用来启动一个线程的。 口Thread类的runO方法是一个线程启动后执行的方法。 15.2.3使用Runnable接口 我们知道,Java中的类只能够是单继承,也就是说,如果一个类为了使用线程而继承 了Thread类,它就不能再继承别的类了。这很可能给编程带来不便。本节中介绍的就是一 种脱离继承来使用线程的方法。这个方法的核心就是Runnable接口。 Runnable接口的全限定名是java.lang.Runnable。它其中只有一个抽象方法void run(O。 为了了解如何在线程中使用Runnable接口,我们还需要看一下Thread类中的一个叫做 target的属性和Thread类中的run(O方法。Thread类中有一个类型为Runnable的属性,叫 做target。而Thread类的runO方法用到了这个属性,runO方法的代码如下: public void run(){ /Thread类的run()方法 if(target!-null){/检查target属性是否为空,target属性是Runnable /1类型的引用 target.run(); /如果不为空则执行run()方法 //否则什么都不做 /run()方法结束 如何让target的值不为null呢?Thread类的另一个构造方法就是用来给target属性赋 值的,这个构造方法是Thread(Runnable)。当调用这个构造方法时,传递过来的参数就 会赋值给target属性。也就是说,如果直接使用Thread类也是可以的,步骤如下: (I)实现Runnable接口,例如叫做MyRunnable,并在MyRunnable类的runO方法里 编写想要让线程做的事情。 (2)创建一个MyRunnable的实例。 (3)通过构造方法Thread(Runnable)来创建Thread类的实例。 这时再调用start()方法启动这个线程,执行的就是MyRunnable中runO方法的代码了。 下面我们来使用以下这种方法,首先是MyRunnable类。 ·446·
第 2 篇 Java 语言高级语法 ·446· 新线程的创建和启动其实都是通过 Java 代码触发的。实际上,除了第一个线程(也 就是启动程序的、运行 main()方法的线程)是由 Java 平台直接创建的之外,其余 的线程都是在 Java 代码中通过“创建 Thread 类的实例,启动线程”这种方式创建 并启动的。 当启动一个新的线程时,其过程是:由 Java 代码通知 Java 平台,Java 平台再启动 线程。例如在图 15-6 中,线程 1 启动线程 2 的过程实际上就是:线程 1 执行 thread.start(),这个方法在内部会让 Java 平台启动第二个线程。所以,启动线程 2 对于线程 1 来说,是一个很短的过程,因为启动线程的具体事情都是 Java 平台做 的,线程 1 只是“通知”Java 平台要启动线程 2 而已,通知完了就继续执行代码, 不等待线程 2。 只有所有的线程都退出以后,程序才会退出。 如果这个过程看上去太抽象,请对比着右边演奏会的过程进行理解。下面学习另一种 使用 Java 线程的方式。 Thread 类的 start()方法是用来启动一个线程的。 Thread 类的 run()方法是一个线程启动后执行的方法。 15.2.3 使用 Runnable 接口 我们知道,Java 中的类只能够是单继承,也就是说,如果一个类为了使用线程而继承 了 Thread 类,它就不能再继承别的类了。这很可能给编程带来不便。本节中介绍的就是一 种脱离继承来使用线程的方法。这个方法的核心就是 Runnable 接口。 Runnable 接口的全限定名是 java.lang.Runnable。它其中只有一个抽象方法 void run()。 为了了解如何在线程中使用 Runnable 接口,我们还需要看一下 Thread 类中的一个叫做 target 的属性和 Thread 类中的 run()方法。Thread 类中有一个类型为 Runnable 的属性,叫 做 target。而 Thread 类的 run()方法用到了这个属性,run()方法的代码如下: public void run() { // Thread 类的 run()方法 if (target != null) { // 检查 target 属性是否为空,target 属性是 Runnable // 类型的引用 target.run(); // 如果不为空则执行 run()方法 } // 否则什么都不做 } // run()方法结束 如何让 target 的值不为 null 呢?Thread 类的另一个构造方法就是用来给 target 属性赋 值的,这个构造方法是 Thread(Runnable)。当调用这个构造方法时,传递过来的参数就 会赋值给 target 属性。也就是说,如果直接使用 Thread 类也是可以的,步骤如下: (1)实现 Runnable 接口,例如叫做 MyRunnable,并在 MyRunnable 类的 run()方法里 编写想要让线程做的事情。 (2)创建一个 MyRunnable 的实例。 (3)通过构造方法 Thread(Runnable)来创建 Thread 类的实例。 这时再调用 start()方法启动这个线程,执行的就是 MyRunnable 中 run()方法的代码了。 下面我们来使用以下这种方法,首先是 MyRunnable 类