Java多线程编程核心技术(txt+pdf+epub+mobi电子书下载)


发布时间:2020-07-02 17:01:17

点击下载

作者:高洪岩

出版社:机械工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

Java多线程编程核心技术

Java多线程编程核心技术试读:

前言

为什么要写这本书

早在几年前笔者就曾想过整理一份与Java多线程有关的稿件,因为市面上所有的Java书籍都是以一章或两章的篇幅介绍多线程技术,并没有完整地覆盖该技术的知识点,但可惜,苦于当时的时间及精力有限,一直没有达成所愿。

也许是注定的安排,我目前所在的单位是集技术与教育为一体的软件类企业。我在工作中发现很多学员在学习完JavaSE/JavaEE之后想对更深入的技术进行探索,比如在对大数据、分布式、高并发类的专题进行攻克时,立即遇到针对java.lang包中Thread类的学习,但Thread类的学习并不像JDBC那样简单,学习多线程会遇到太多的问题、弯路以及我们所谓的“坑”,为了带领学员在技术层面上进行更高的追求,我将多线程的技术点以教案的方式进行整理,在课堂上与同学们一起学习、交流,同学们反响也非常热烈。此至,若干年前的心愿终于了却,学员们也很期待这本书能出版发行,因为这样他们就有了真正的纸质参考资料,其他爱好Java多线程的朋友们也在期盼本书的出版。本书能促进他们相互交流与学习,这就是我最大的心愿。

本书秉承大道至简的主导思想,只介绍Java多线程开发中最值得关注的内容,希望能抛砖引玉,以个人的一些想法和见解,为读者拓展出更深入、更全面的思路。

本书特色

在本书写作的过程中,我尽量减少“啰嗦”的文字语言,全部用案例来讲解技术点的实现,使读者看到代码及运行结果后就可以知道此项目要解决的是什么问题,类似于网络中的博客风格,可让读者用最短的时间学完相关知识点,明白这些知识点是如何应用的,以及在使用时要避免什么。本书就像“瑞士军刀”一样,精短小,但却非常锋利,可帮读者快速学习知识并解决问题。

读者对象

本书适合所有Java程序员阅读,尤其适合以下读者:

·Java多线程开发者

·Java并发开发者

·系统架构师

·大数据开发者

·其他对多线程技术感兴趣的人员

如何阅读本书

在整理本书时,我一直本着实用、易懂的原则,最终整理出7章:

第1章讲解了Java多线程的基础,包括Thread类的核心API的使用。

第2章讲解了在多线程中对并发访问的控制,主要就是synchronized的使用,由于此关键字在使用上非常灵活,所以书中用了很多案例来介绍此关键字的使用,为读者学习同步相关内容打好坚实的基础。

第3章介绍线程并不是孤独的,它们之间要通信,要交互。本章主要介绍wait()、notifyAll()和notify()方法的使用,使线程间能互相通信,合作完成任务。本章还介绍了ThreadLocal类的使用。学习完本章,读者就能在Thread多线程中进行数据的传递了。

第4章讲解了synchronized关键字,它使用起来比较麻烦,所以在Java5中提供了Lock对象,以求能更好地实现并发访问时的同步处理,包括读写锁等相关技术点。

第5章讲解了Timer定时器类,其内部实现就是使用的多线程技术。定时器的计划任务执行是很重要的技术点,包括在Android开发时都会有深入的使用,所以会为读者详细讲解。

第6章讲解的单例模式虽然很简单,但如果遇到多线程将会变得非常麻烦,如何在多线程中解决这么棘手的问题呢?本章将全面介绍解决方案。

第7章,在整理稿件的过程中肯定会出现一些技术知识点的空缺,前面被遗漏的技术案例将在本章进行补充,以帮助读者形成完整的多线程的知识体系。编写本章的目的就是尽量使本书不存在技术空白点。

勘误和支持

由于我的水平有限,编写时间仓促,书中难免会出现一些错误或者不准确的地方,恳请读者批评指正,让我与大家一起,在技术之路上互勉共进。我的邮箱是279377921@qq.com,期待能够得到你们的真挚反馈。本书的源代码可以在华章网站(www.hzbook.com)下载。

致谢

感谢所在单位领导的支持与厚爱,使我在技术道路上更有信心。

感谢机械工业出版社华章公司的高婧雅和杨福川,因为有了你们的鼓励、帮助和引导,我才能顺利完成本书。高洪岩  第1章Java多线程技能

作为本书的第1章,一定要引导读者快速进入Java多线程的学习,所以本章中主要介绍Thread类中的核心方法。Thread类的核心方法较多,读者应该着重掌握如下关键技术点:

·线程的启动

·如何使线程暂停

·如何使线程停止

·线程的优先级

·线程安全相关的问题

上面的5点也是本章学习的重点与思路,掌握这些内容是学习Java多线程的必经之路。1.1 进程和多线程的概念及线程的优点

本节主要介绍在Java语言中使用多线程技术。但讲到多线程这个技术时不得不提及“进程”这个概念,“百度百科”里对“进程”的解释如图1-1所示。图1-1 进程的解释

初看这段文字会觉得十分的抽象,难以理解,但如果你看到图1-2所示的内容,那么你对进程还不能理解吗?图1-2 Windows7系统中的进程列表

难道可以将一个正在操作系统中运行的exe程序理解成一个“进程”吗?没错!

通过查看“Windows任务管理器”中的列表,完全可以将运行在内存中的exe文件理解成进程,进程是受操作系统管理的基本运行单元。

那什么是线程呢?线程可以理解成是在进程中独立运行的子任务。比如,QQ.exe运行时就有很多的子任务在同时运行。再如,好友视频线程、下载文件线程、传输数据线程、发送表情线程等,这些不同的任务或者说功能都可以同时运行,其中每一项任务完全可以理解成是“线程”在工作,传文件、听音乐、发送图片表情等功能都有对应的线程在后台默默地运行。

这样做有什么优点呢?更具体来讲,使用多线程有什么优点呢?其实如果读者有使用“多任务操作系统”的经验,比如Windows系列,那么它的方便性大家应该都有体会:使用多任务操作系统Windows后,可以最大限度地利用CPU的空闲时间来处理其他的任务,比如一边让操作系统处理正在由打印机打印的数据,一边使用Word编辑文档。而CPU在这些任务之间不停地切换,由于切换的速度非常快,给使用者的感受就是这些任务似乎在同时运行。所以使用多线程技术后,可以在同一时间内运行更多不同种类的任务。

为了更加有效地理解多线程的优势,看一下如图1-3所示的单任务的模型图,理解一下单任务的缺点。

在图1-3中,任务1和任务2是两个完全独立、互不相关的任务,任务1是在等待远程服务器返回数据,以便进行后期的处理,这时CPU一直处于等待状态,一直在“空运行”。如果任务2是在10秒之后被运行,虽然执行任务2用的时间非常短,仅仅是1秒,但也必须在任务1运行结束后才可以运行任务2。本程序是运行在单任务环境中,所以任务2有非常长的等待时间,系统运行效率大幅降低。单任务的特点就是排队执行,也就是同步,就像在cmd中输入一条命令后,必须等待这条命令执行完才可以执行下一条命令一样。这就是单任务环境的缺点,即CPU利用率大幅降低。

而多任务的环境如图1-4所示。图1-3 单任务运行环境图1-4 多任务运行环境

在图1-4中可以发现,CPU完全可以在任务1和任务2之间来回切换,使任务2不必等到10秒再运行,系统的运行效率大大得到提升。这就是要使用多线程技术、要学习多线程的原因。这是多线程技术的优点,使用多线程也就是在使用异步。

注意 多线程是异步的,所以千万不要把Eclipse里代码的顺序当成线程执行的顺序,线程被调用的时机是随机的。1.2 使用多线程

想学习一个技术就要“接近”它,所以在本节,首先用一个示例来接触一下线程。

一个进程正在运行时至少会有1个线程在运行,这种情况在Java中也是存在的。这些线程在后台默默地执行,比如调用public static void main()方法的线程就是这样的,而且它是由JVM创建的。

创建示例项目callMainMethodMainThread,创建Test.java类。代码如下:package test;public class Test {public static void main(String[] args) { System.out.println(Thread.currentThread().getName()); }}

程序运行后的效果如图1-5所示。图1-5 主线程main

在控制台中输出的main其实就是一个名称叫作main的线程在执行main()方法中的代码。另外需要说明一下,在控制台输出的main和main方法没有任何的关系,仅仅是名字相同而已。1.2.1 继承Thread类

在Java的JDK开发包中,已经自带了对多线程技术的支持,可以很方便地进行多线程编程。实现多线程编程的方式主要有两种,一种是继承Thread类,另一种是实现Runnable接口。

但在学习如何创建新的线程前,先来看看Thread类的结构,如下:public class Thread implements Runnable

从上面的源代码中可以发现,Thread类实现了Runnable接口,它们之间具有多态关系。

其实,使用继承Thread类的方式创建新线程时,最大的局限就是不支持多继承,因为Java语言的特点就是单根继承,所以为了支持多继承,完全可以实现Runnable接口的方式,一边实现一边继承。但用这两种方式创建的线程在工作时的性质是一样的,没有本质的区别。

本节来看一下第一种方法。创建名称为t1的Java项目,创建一个自定义的线程类MyThread.java,此类继承自Thread,并且重写run方法。在run方法中,写线程要执行的任务的代码如下:package com.mythread.www;public class MyThread extends Thread { @Override public void run() { super.run(); System.out.println("MyThread"); }}

运行类代码如下:package test;import com.mythread.www.MyThread;public class Run { public static void main(String[] args) { MyThread mythread = new MyThread(); mythread.start(); System.out.println("运行结束!"); }}

运行结果如图1-6所示。图1-6 运行结果

从图1-6中的运行结果来看,MyThread.java类中的run方法执行的时间比较晚,这也说明在使用多线程技术时,代码的运行结果与代码执行顺序或调用顺序是无关的。

线程是一个子任务,CPU以不确定的方式,或者说是以随机的时间来调用线程中的run方法,所以就会出现先打印“运行结束!”后输出“MyThread”这样的结果了。

注意 如果多次调用start()方法,则会出现异常Exception in thread"main"java.lang.IllegalThreadStateException。

上面介绍了线程的调用的随机性,下面将在名称为randomThread的Java项目中演示线程的随机性。

创建自定义线程类MyThread.java,代码如下:package mythread;public class MyThread extends Thread { @Override public void run() { try { for (int i = 0; i < 10; i++) { int time = (int) (Math.random() * 1000); Thread.sleep(time); System.out.println("run=" + Thread.currentThread().getName()); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }}

再创建运行类Test.java,代码如下:package test;import mythread.MyThread;public class Test { public static void main(String[] args) { try { MyThread thread = new MyThread(); thread.setName("myThread"); thread.start(); for (int i = 0; i < 10; i++) { int time = (int) (Math.random() * 1000); Thread.sleep(time); System.out.println("main=" + Thread.currentThread().getName()); } } catch (InterruptedException e) { e.printStackTrace(); } }}

在代码中,为了展现出线程具有随机特性,所以使用随机数的形式来使线程得到挂起的效果,从而表现出CPU执行哪个线程具有不确定性。

Thread.java类中的start()方法通知“线程规划器”此线程已经准备就绪,等待调用线程对象的run()方法。这个过程其实就是让系统安排一个时间来调用Thread中的run()方法,也就是使线程得到运行,启动线程,具有异步执行的效果。如果调用代码thread.run()就不是异步执行了,而是同步,那么此线程对象并不交给“线程规划器”来进行处理,而是由main主线程来调用run()方法,也就是必须等run()方法中的代码执行完后才可以执行后面的代码。

以异步的方式运行的效果如图1-7所示。图1-7 随机被执行的线程

另外还需要注意一下,执行start()方法的顺序不代表线程启动的顺序。创建测试用的项目名称为z,类MyThread.java代码如下:package extthread;public class MyThread extends Thread { private int i; public MyThread(int i) { super(); this.i = i; } @Override public void run() { System.out.println(i); }}

运行类Test.java代码如下:package test;import extthread.MyThread;public class Test { public static void main(String[] args) { MyThread t11 = new MyThread(1); MyThread t12 = new MyThread(2); MyThread t13 = new MyThread(3); MyThread t14 = new MyThread(4); MyThread t15 = new MyThread(5); MyThread t16 = new MyThread(6); MyThread t17 = new MyThread(7); MyThread t18 = new MyThread(8); MyThread t19 = new MyThread(9); MyThread t110 = new MyThread(10); MyThread t111 = new MyThread(11); MyThread t112 = new MyThread(12); MyThread t113 = new MyThread(13); t11.start(); t12.start(); t13.start(); t14.start(); t15.start(); t16.start(); t17.start(); t18.start(); t19.start(); t110.start(); t111.start(); t112.start(); t113.start(); }}

程序运行后的结果如图1-8所示。图1-8 线程启动顺序与start()执行顺序无关1.2.2 实现Runnable接口

如果欲创建的线程类已经有一个父类了,这时就不能再继承自Thread类了,因为Java不支持多继承,所以就需要实现Runnable接口来应对这样的情况。

创建项目t2,继续创建一个实现Runnable接口的类MyRunnable,代码如下:package myrunnable;public class MyRunnable implements Runnable { @Override public void run() { System.out.println("运行中!"); }}

如何使用这个MyRunnable.java类呢?这就要看一下Thread.java的构造函数了,如图1-9所示。图1-9 Thread构造函数

在Thread.java类的8个构造函数中,有两个构造函数Thread(Runnable target)和Thread(Runnable target,String name)可以传递Runnable接口,说明构造函数支持传入一个Runnable接口的对象。运行类代码如下:public class Run { public static void main(String[] args) { Runnable runnable=new MyRunnable(); Thread thread=new Thread(runnable); thread.start(); System.out.println("运行结束!"); }}

运行结果如图1-10所示。

图1-10所示的打印结果没有什么特殊之处。

使用继承Thread类的方式来开发多线程应用程序在设计上是有局限性的,因为Java是单根继承,不支持多继承,所以为了改变这种限制,可以使用实现Runnable接口的方式来实现多线程技术。这也是上面的示例介绍的知识点。

另外需要说明的是,Thread.java类也实现了Runnable接口,如图1-11所示。图1-10 运行结果图1-11 类Thread实现Runnable接口

那也就意味着构造函数Thread(Runnable target)不光可以传入Runnable接口的对象,还可以传入一个Thread类的对象,这样做完全可以将一个Thread对象中的run()方法交由其他的线程进行调用。1.2.3 实例变量与线程安全

自定义线程类中的实例变量针对其他线程可以有共享与不共享之分,这在多个线程之间进行交互时是很重要的一个技术点。(1)不共享数据的情况

不共享数据的情况如图1-12所示。图1-12 不共享数据

下面通过一个示例来看下数据不共享情况。

创建实验用的Java项目,名称为t3,MyThread.java类代码如下:public class MyThread extends Thread { private int count = 5; public MyThread(String name) { super(); this.setName(name);//设置线程名称 } @Override public void run() { super.run(); while (count > 0) { count--; System.out.println("由 " + this.currentThread().getName() + " 计算,count=" + count); } }}

运行类Run.java代码如下:public class Run { public static void main(String[] args) { MyThread a=new MyThread("A"); MyThread b=new MyThread("B"); MyThread c=new MyThread("C"); a.start(); b.start(); c.start(); }}

不共享数据运行结果如图1-13所示。

由图1-13可以看到,一共创建了3个线程,每个线程都有各自的count变量,自己减少自己的count变量的值。这样的情况就是变量不共享,此示例并不存在多个线程访问同一个实例变量的情况。

如果想实现3个线程共同对一个count变量进行减法操作的目的,该如何设计代码呢?(2)共享数据的情况

共享数据的情况如图1-14所示。图1-13 不共享数据的运行结果图1-14 共享数据

共享数据的情况就是多个线程可以访问同一个变量,比如在实现投票功能的软件时,多个线程可以同时处理同一个人的票数。

下面通过一个示例来看下数据共享情况。

创建t4测试项目,MyThread.java类代码如下:public class MyThread extends Thread { private int count=5; @Override public void run() { super.run(); count--;//此示例不要用for语句,因为使用同步后其他线程就得不到运行的机会了,//一直由一个线程进行减法运算 System.out.println("由 "+this.currentThread().getName()+" 计算,count="+count); }}

运行类Run.java代码如下:public class Run { public static void main(String[] args) { MyThread mythread=new MyThread(); Thread a=new Thread(mythread,"A"); Thread b=new Thread(mythread,"B"); Thread c=new Thread(mythread,"C"); Thread d=new Thread(mythread,"D"); Thread e=new Thread(mythread,"E"); a.start(); b.start(); c.start(); d.start(); e.start(); }}

运行结果如图1-15所示。图1-15 共享数据运行结果

从图1-15中可以看到,线程A和B打印出的count值都是3,说明A和B同时对count进行处理,产生了“非线程安全”问题。而我们想要得到的打印结果却不是重复的,而是依次递减的。

在某些JVM中,i--的操作要分成如下3步:

1)取得原有i值。

2)计算i-1。

3)对i进行赋值。

在这3个步骤中,如果有多个线程同时访问,那么一定会出现非线程安全问题。

其实这个示例就是典型的销售场景:5个销售员,每个销售员卖出一个货品后不可以得出相同的剩余数量,必须在每一个销售员卖完一个货品后其他销售员才可以在新的剩余物品数上继续减1操作。这时就需要使多个线程之间进行同步,也就是用按顺序排队的方式进行减1操作。更改代码如下:public class MyThread extends Thread { private int count=5; @Override synchronized public void run() { super.run(); count--; System.out.println("由 "+this.currentThread().getName()+" 计算,count="+count); }}

重新运行程序,就不会出现值一样的情况了,如图1-16所示。图1-16  方法调用被同步

通过在run方法前加入synchronized关键字,使多个线程在执行run方法时,以排队的方式进行处理。当一个线程调用run前,先判断run方法有没有被上锁,如果上锁,说明有其他线程正在调用run方法,必须等其他线程对run方法调用结束后才可以执行run方法。这样也就实现了排队调用run方法的目的,也就达到了按顺序对count变量减1的效果了。synchronized可以在任意对象及方法上加锁,而加锁的这段代码称为“互斥区”或“临界区”。

当一个线程想要执行同步方法里面的代码时,线程首先尝试去拿这把锁,如果能够拿到这把锁,那么这个线程就可以执行synchronize里面的代码。如果不能拿到这把锁,那么这个线程就会不断地尝试拿这把锁,直到能够拿到为止,而且是有多个线程同时去争抢这把锁。

本节中出现了一个术语“非线程安全”。非线程安全主要是指多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改、值不同步的情况,进而影响程序的执行流程。下面再用一个示例来学习一下如何解决“非线程安全”问题。

创建t4_threadsafe项目,来实现一下非线程安全的环境。LoginServlet.java代码如下:package controller;//本类模拟成一个Servlet组件public class LoginServlet { private static String usernameRef; private static String passwordRef; public static void doPost(String username, String password) { try { usernameRef = username; if (username.equals("a")) { Thread.sleep(5000); } passwordRef = password; System.out.println("username=" + usernameRef + " password=" + password); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }}

线程ALogin.java代码如下:package extthread;import controller.LoginServlet;public class ALogin extends Thread { @Override public void run() { LoginServlet.doPost("a", "aa"); }}

线程BLogin.java代码如下:package extthread;import controller.LoginServlet;public class BLogin extends Thread { @Override public void run() { LoginServlet.doPost("b", "bb"); }}

运行类Run.java代码如下:public class Run { public static void main(String[] args) { ALogin a = new ALogin(); a.start(); BLogin b = new BLogin(); b.start(); }}

程序运行后的效果如图1-17所示。

解决这个“非线程安全”的方法也是使用synchronized关键字。更改代码如下: synchronized public static void doPost(String username, String password) { try { usernameRef = username; if (username.equals("a")) { Thread.sleep(5000); } passwordRef = password; System.out.println("username=" + usernameRef + " password=" + password); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }

程序运行后效果如图1-18所示。图1-17 非线程安全图1-18 排队进入方法1.2.4 留意i--与System.out.println()的异常

在前面章节中,解决非线程安全问题使用的是synchronized关键字,本节将通过程序案例细化一下println()方法与i++联合使用时“有可能”出现的另外一种异常情况,并说明其中的原因。

创建名称为sameNum的项目,自定义线程MyThread.java代码如下:package extthread;public class MyThread extends Thread { private int i = 5; @Override public void run() { System.out.println("i=" + (i--) + " threadName=" + Thread.currentThread().getName()); //注意:代码i--由前面项目中单独一行运行改成在当前项目中在println()方法中直接进行打印 }}

运行类Run.java代码如下:package test;import extthread.MyThread;public class Run { public static void main(String[] args) { MyThread run = new MyThread(); Thread t1 = new Thread(run); Thread t2 = new Thread(run); Thread t3 = new Thread(run); Thread t4 = new Thread(run); Thread t5 = new Thread(run); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); }}

程序运行后根据概率还是会出现非线程安全问题,如图1-19所示。图1-19 出现非线程安全问题

本实验的测试目的是:虽然println()方法在内部是同步的,但i--的操作却是在进入println()之前发生的,所以有发生非线程安全问题的概率,如图1-20所示。图1-20 println内部同步

所以,为了防止发生非线程安全问题,还是应继续使用同步方法。1.3 currentThread()方法

currentThread()方法可返回代码段正在被哪个线程调用的信息。下面通过一个示例进行说明。

创建t6项目,创建Run1.java类代码如下:public class Run1 { public static void main(String[] args) { System.out.println(Thread.currentThread().getName()); }}

程序运行结果如图1-21所示。图1-21 运行结果

结果说明,main方法被名为main的线程调用。

继续实验,创建MyThread.java类。代码如下:public class MyThread extends Thread { public MyThread() { System.out.println("构造方法的打印:" + Thread.currentThread().getName()); } @Override public void run() { System.out.println("run方法的打印:" + Thread.currentThread().getName()); }}

运行类Run2.java代码如下:public class Run2 { public static void main(String[] args) { MyThread mythread = new MyThread(); mythread.start(); // mythread.run(); }}

程序运行结果如图1-22所示。

从图1-22中的运行结果可以发现,MyThread.java类的构造函数是被main线程调用的,而run方法是被名称为Thread-0的线程调用的,run方法是自动调用的方法。

文件Run2.java代码更改如下:public class Run2 { public static void main(String[] args) { MyThread mythread = new MyThread(); // mythread.start(); mythread.run(); }}

运行结果如图1-23所示。图1-22 运行结果图1-23 均被main主线程所调用

再来测试一个比较复杂的情况,创建测试用的项目currentThreadExt,创建Java文件CountOperate.java。代码如下:package mythread;public class CountOperate extends Thread { public CountOperate() { System.out.println("CountOperate---begin"); System.out.println("Thread.currentThread().getName()=" + Thread.currentThread().getName()); System.out.println("this.getName()=" + this.getName()); System.out.println("CountOperate---end"); } @Override public void run() { System.out.println("run---begin"); System.out.println("Thread.currentThread().getName()=" + Thread.currentThread().getName()); System.out.println("this.getName()=" + this.getName()); System.out.println("run---end"); }}

创建Run.java文件,代码如下:package test;import mythread.CountOperate;public class Run { public static void main(String[] args) { CountOperate c = new CountOperate(); Thread t1 = new Thread(c); t1.setName("A"); t1.start(); }}

程序运行结果如下:CountOperate---beginThread.currentThread().getName()=mainthis.getName()=Thread-0CountOperate---endrun---beginThread.currentThread().getName()=Athis.getName()=Thread-0run---end1.4 isAlive()方法

方法isAlive()的功能是判断当前的线程是否处于活动状态。

新建项目t7,类文件MyThread.java代码如下:public class MyThread extends Thread { @Override public void run() { System.out.println("run=" + this.isAlive()); }}

运行Run.java代码如下:public class Run { public static void main(String[] args) { MyThread mythread = new MyThread(); System.out.println("begin ==" + mythread.isAlive()); mythread.start(); System.out.println("end ==" + mythread.isAlive()); }}

程序运行结果如图1-24所示。图1-24 运行结果

方法isAlive()的作用是测试线程是否处于活动状态。什么是活动状态呢?活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的。

需要说明一下,如以下代码:System.out.println("end ==" + mythread.isAlive());

虽然在上面的示例中打印的值是true,但此值是不确定的。打印true值是因为mythread线程还未执行完毕,所以输出true。如果代码更改如下: public static void main(String[] args) throws InterruptedException { MyThread mythread = new MyThread(); System.out.println("begin ==" + mythread.isAlive()); mythread.start(); Thread.sleep(1000); System.out.println("end ==" + mythread.isAlive()); }

则上述代码运行的结果输出为false,因为mythread对象已经在1秒之内执行完毕。

另外,在使用isAlive()方法时,如果将线程对象以构造参数的方式传递给Thread对象进行start()启动时,运行的结果和前面示例是有差异的。造成这样的差异的原因还是来自于Thread.currentThread()和this的差异。下面测试一下这个实验。

创建测试用的isaliveOtherTest项目,创建CountOperate.java文件,代码如下:package mythread;public class CountOperate extends Thread { public CountOperate() { System.out.println("CountOperate---begin"); System.out.println("Thread.currentThread().getName()=" + Thread.currentThread().getName()); System.out.println("Thread.currentThread().isAlive()=" + Thread.currentThread().isAlive()); System.out.println("this.getName()=" + this.getName()); System.out.println("this.isAlive()=" + this.isAlive()); System.out.println("CountOperate---end"); } @Override public void run() { System.out.println("run---begin"); System.out.println("Thread.currentThread().getName()=" + Thread.currentThread().getName()); System.out.println("Thread.currentThread().isAlive()=" + Thread.currentThread().isAlive()); System.out.println("this.getName()=" + this.getName()); System.out.println("this.isAlive()=" + this.isAlive()); System.out.println("run---end"); }}

创建Run.java文件,代码如下:package test;import mythread.CountOperate;public class Run { public static void main(String[] args) { CountOperate c = new CountOperate(); Thread t1 = new Thread(c); System.out.println("main begin t1 isAlive=" + t1.isAlive()); t1.setName("A"); t1.start(); System.out.println("main end t1 isAlive=" + t1.isAlive()); }}

程序运行结果如下:CountOperate---beginThread.currentThread().getName()=mainThread.currentThread().isAlive()=truethis.getName()=Thread-0this.isAlive()=falseCountOperate---endmain begin t1 isAlive=falsemain end t1 isAlive=truerun---beginThread.currentThread().getName()=AThread.currentThread().isAlive()=truethis.getName()=Thread-0this.isAlive()=falserun---end1.5 sleep()方法

方法sleep()的作用是在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行)。这个“正在执行的线程”是指this.currentThread()返回的线程。

通过一个示例进行说明。创建项目t8,类MyThread1.java代码如下:public class MyThread1 extends Thread { @Override public void run() { try { System.out.println("run threadName=" + this.currentThread().getName() + " begin"); Thread.sleep(2000); System.out.println("run threadName=" + this.currentThread().getName() + " end"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }}

运行类Run1.java代码如下:public class Run1 { public static void main(String[] args) { MyThread1 mythread = new MyThread1(); System.out.println("begin =" + System.currentTimeMillis()); mythread.run(); System.out.println("end =" + System.currentTimeMillis()); }}

直接调用run()方法,程序运行结果如图1-25所示。

继续实验,创建MyThread2.java代码如下:public class MyThread2 extends Thread { @Override public void run() { try { System.out.println("run threadName=" + this.currentThread().getName() + " begin =" + System.currentTimeMillis()); Thread.sleep(2000); System.out.println("run threadName=" + this.currentThread().getName() + " end =" + System.currentTimeMillis()); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }}

创建Run2.java代码如下:public class Run2 { public static void main(String[] args) { MyThread2 mythread = new MyThread2(); System.out.println("begin =" + System.currentTimeMillis()); mythread.start(); System.out.println("end =" + System.currentTimeMillis()); }}

使用start()方法启动线程,程序运行结果如图1-26所示。图1-25 将main线程暂停了2秒图1-26 运行结果

由于main线程与MyThread2线程是异步执行的,所以首先打印的信息为begin和end。而MyThread2线程是随后运行的,在最后两行打印run begin和run end相关的信息。1.6 getId()方法

getId()方法的作用是取得线程的唯一标识。

创建测试用的项目runThread,创建Test.java类,代码如下:package test;public class Test { public static void main(String[] args) { Thread runThread = Thread.currentThread(); System.out.println(runThread.getName() + " " + runThread.getId()); }}

程序运行后的效果如图1-27所示。图1-27 获取线程名称及id值

从打印的运行结果来看,当前执行代码的线程名称为main,线程id值为1。1.7 停止线程

停止线程是在多线程开发时很重要的技术点,掌握此技术可以对线程的停止进行有效的处理。停止线程在Java语言中并不像break语句那样干脆,需要一些技巧性的处理。

使用Java内置支持多线程的类设计多线程应用是很常见的事情,然而,多线程给开发人员带来了一些新的挑战,如果处理不好就会导致超出预期的行为并且难以定位错误。

本节将讨论如何更好地停止一个线程。停止一个线程意味着在线程处理完任务之前停掉正在做的操作,也就是放弃当前的操作。虽然这看起来非常简单,但是必须做好防范措施,以便达到预期的效果。停止一个线程可以使用Thread.stop()方法,但最好不用它。虽然它确实可以停止一个正在运行的线程,但是这个方法是不安全的(unsafe),而且是已被弃用作废的(deprecated),在将来的Java版本中,这个方法将不可用或不被支持。

大多数停止一个线程的操作使用Thread.interrupt()方法,尽管方法的名称是“停止,中止”的意思,但这个方法不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。关于此知识点在后面有专门的章节进行介绍。

在Java中有以下3种方法可以终止正在运行的线程:

1)使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。

2)使用stop方法强行终止线程,但是不推荐使用这个方法,因为stop和suspend及resume一样,都是作废过期的方法,使用它们可能产生不可预料的结果。

3)使用interrupt方法中断线程。

这3种方法都会在后面的章节进行介绍。

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载