龙空技术网

Java多线程编程学习笔记(一)

Java架构师追风 148

前言:

此时兄弟们对“java多线程写一个文件”大约比较关注,咱们都想要知道一些“java多线程写一个文件”的相关资讯。那么小编同时在网络上搜集了一些有关“java多线程写一个文件””的相关资讯,希望我们能喜欢,大家一起来了解一下吧!

之前有很多同学跟我说多线程学得很头疼,完全没有头绪,所以这篇文章主要是分享一下我学多线程的一些学习笔记,由于篇幅所限,本文就只大概讲一下多进程多线程概述和多线程一些概念的解释,以及一些简单的小实例。

至于线程同步、线程通讯、守护线程蔚县城的优先级、线程池的实现以及线程状态监控等等等等就先不写了,实在不是一两句话能够写完的,如果感兴趣的同学多,我后面在更新吧。

所以如果你觉得本文写得还可以的话,请转发+关注+评论,让他被更多人看到,先提前感谢[狗头]

文末也给大家推荐了一些不错的相关书籍,这些书的电子档我也都有收集,需要的同学转发本文+关注+私信【1205】即可白嫖!

想要资料的同学一定要先关注哦,现在头条陌生人的消息是收不到的!!

一、多进程多线程概述

1.1 什么是多进程多线程

多进程多线程的具体概念到底是如何,我也基本上告别操作系统那本书很久了,所以也不会有官方的答案,但是比较山寨通俗化的理解还是有的,至于官方正式的描述读者可以自行查阅相关资料

什么是进程

进程就是 CPU 的执行路径,呵呵,这个也许也有些抽象,那我们就来一个更为通俗的说法,进程就是系统运行中的程序,比如说我们打开浏览器,打开 word,打开魔兽世界, 他们就是程序,他们就是所谓的 CPU 执行路径,当然他们也就是进程,如果还不明白,那你就打开任务管理器,看看进程那一栏就明白了;

什么是多进程

多进程就是系统中可以同时运行多个程序,比如我们在浏览网页的时候可以打开 office, 将网页中的内容粘贴到 word 之中,我们同时运行了浏览器和 word,这就是所谓的多进程, 其实没有真正意义上的多进程,我们在后文中在进行讲解;先可以这么理解和认为;当然这是不够严谨的哦!

什么是线程

线程就是运行在进程当中的运行单元,比如说我们打开迅雷(它是一个运行程序,因此它是一个进程),我们下载的某个任务他就是一个线程;

什么是多进程

同样的道理,每个进程里面有多个独立的或者相互有协作关系的运行单元我们就称之为多线程,比如说我们可以通过迅雷同时下载多个文件,并且还可以上传某个文件,当然也没

有严格意义的多线程(多核 CPU 就另当别论了)

咬文嚼字

进程英文单词 process,有运行的意思,顾名思义,他必须是运行着的才能称之为进程; 线程英文单词 thread,有丝线的意思,就是颗粒很细,力度很小,因此他要依附于进程,

所以我们可以姑且这样认为,没有进程肯定谈不上有线程;

1.2 Java 对多线程的支持

我们都知道,在任何一门语言中,程序的执行顺序都是顺序执行的,也就是说执行完 A

部分的代码片段之后才能执行B 代码片段,当然多线程的情况除外,请看下面的代码;

package com.wenhuisoft.chapter;public class ThreadSupport{public static void main(String[] args){int i = 100;while(i>0){System.out.println("current i value is:"+i--);}System.out.println("==============================");while(i<100){System.out.println("current i value is :"+i++);}}}

只有在结束了第一个循环之后才能进入第二个循环,两者并不能同时执行,如果我们引入多线程的概念,该问题将迎刃而解,Java 对多线程的支持和封装是目前所有语言中做的最好的,它让开发人员远离了 C 程序员那些信号量等繁琐的编程方式,只关注共享的数据,关注业务逻辑单元,不去关注细节;

Java 中多线程的实现方式有两种,这里只做一个简单的介绍,在后文中将会详细的讲解

这两种的具体实现方式和优缺点,以及工作中如何是使用线程;

继承 Thread 父类;实现 Runnable 接口;

其实 JDK 的编码过程中涉及到了很多个设计模式,其中 Thread 就用到了template design pattern 和 strategy design pattern 在后文中我会一一详细讲解,并且教大家如何使用这两个设计模式,这两个模式也是我本人非常喜欢和常用的设计思想;

1.3 第一个多线程程序

接着上面的示例,我们快速实现一个多线程的实现,来看看我们的程序如何交替执行从1~100 然后从 100~1 的输出,其中的一些技术细节我们会慢慢的讨论,如果看不懂不要着急,我都会逐一进行讲解;

package com.wenhuisoft.chapter;public class FirstThread{public static void main(String[] args){new Thread(new Runnable(){public void run(){int i = 0;while(i<100){System.out.println(Thread.currentThread().getName()+":"+i++);}}}).start();int i = 100;while(i>0){System.out.println(Thread.currentThread().getName()+":"+i--);}}

读者可以运行上面的例子,发现程序实现了交替打印的功能,但是我们的目的不止于此, 我们需要对其进行深究;

1.3.1 没有真正意义上的多线程

了解 CPU(单核)的人都知道,CPU 在同一个时刻只能给一个程序分配资源,也就是赋予一个程序运行权,那么我们看到一次能运行好几个程序其实是 CPU 来回切换执行权,所以让别人以为是并发运行,只是切换的速度很快(取决于 CPU 的主频)所以没有真正意义上的并发;

1.3.2 纠结的思考

其实刚才的程序我们看到的已经有两个线程的存在了,我之前说过,一个进程至少有一个执行单元,可以理解为至少有一个线程在运行,那么 main 函数应该就是一个线程,因为它是程序的入口,然后我们又写了一个匿名类它显示的实现了 Runnable 接口,并且构造了一个 Thread 类,因此它也是一个线程,因此有两个线程在同时运行

问题往往没有那么简单,这也是本小节名字“纠结的思考”的来源,他真的是两个线程么?难道 JVM 不做些什么吗?最起码我们应该联想到它应该有一个后台线程负责管理堆栈信息管理垃圾的清理工作啊,因此上述的代码远远不止于一个线程,但是往往这样的钻牛角尖会让我们学习 Thread API 产生很多顾虑,因此我们可以暂且不用去管 JVM 在有多少个线程在支撑着我们的程序,但是我们最起码应该有这样的意识,这样也不至于在学习的路上浅尝辄止。

1.3.3 线程初探总结

Main 函数本身其实就是一个线程,我们称他为主线程;实现多线程我们可以继承 Thread 类,也可以继承 Runnable 接口;没有严格意义上的并发;JVM 自身有很多后台线程在运行;二、多线程详解

本章的内容应该是本文中最为重要的部分,因为我会循序渐进的引出多线程的创建,线程的生命周期,并且以一个多窗口出票程序来说明多线程如何工作,还有会有我之前承诺大家的两个设计模式(Template,strategy)

2.1 继承 Thread 创建线程

通过翻阅 JDK API 我们发现文档中重点描述到,实现一个线程的一种方式为成为 Thread 的子类,也就是继承 Thread,然后重写 run 方法,其中 run 方法是线程的执行代码片段;综合这段话,我们可以总结创建并运行一个线程有三个步骤;

继承 Thread 类; 重写 run 方法;调用线程的start 方法(启动线程,调用 run 方法)

其实这段代码我们之前已经写过了,但是为了说明问题,我们还是在写一个简单的线程

来说明上述的三个观点

package com.wenhuisoft.chapter;public class MyThread extends Thread{@Overridepublic void run(){int i = 0;while(i<60){System.out.println("i:"+i++);}}public static void main(String[] args){MyThread thread = new MyThread(); thread.start();}}

首先我们继承了 Thread 类,我们也重写了 run 方法,我们也在 main 方法中构造了一个

Thread 类然后调用了 start 方法;因此线程被创建并且被运行了

2.1.1 你的疑惑曾经是我的疑惑

为什么我重写的是 run 方法,却要调用 start 方法来启动它,我们如果直接调用线程实例的 run 方法不行么?可以,当然可以因为它是成员函数,调用当然是无可厚非的事情了, 但是为什么他不代表启动了线程呢?也许看到这里你有这样的疑惑,我希望我通过我的认识和理解不仅能说清楚start 做了什么,并且能告诉你一种设计方法;

2.1.2 父类实现算法,子类实现细节

在程序的设计中我们经常会将算法进行抽象,因为它有很多种运算的可能,所以我们为了更好地扩展,我们将算法进行了抽象,并且统一交给父类进行实现,子类只需要知道某个单元模块的功能即可,具体是如何穿插起来的子类不用去关心,我们实现一个输出图形的算法,然后有两种不同的实现,为了明显期间,我将所有的类写在一个文件中

package com.wenhuisoft.chapter;abstract class Diagram{protected char c ;public Diagram(char c){this.c = c;}abstract protected void print(int size);abstract protected void printContent(String msg);public final void display(String msg){int len = msg.getBytes().length; print(len);printContent(msg); print(len);}}class StarDiagram extends Diagram{public StarDiagram(char c){super(c);}@Overrideprotected void print(int size){for(int i = 0;i<size+2;i++){System.out.print(c);}System.out.println();}@Overrideprotected void printContent(String msg){System.out.print("*"); System.out.print(msg); System.out.println("*");}}public class TemplateTest{public static void main(String[] args){Diagram d1 = new StarDiagram('*'); d1.display("wangwenjun");}}

我们可以看到父类 Diagram 中的 display 方法规范了算法,也就是将打印上线边线和输出内容的部分进行了算法约束,我们不用关心他的算法逻辑,我们只需要实现他所抽象的两

个方法 print 和 printContent 方法,奇怪我们明明实现的是这两个方法,但是为什么运行的时候需要调用 display 方法呢?这样的问题是否似曾相识呢?是否在前面也同样有过这样的疑问呢?为什么我实现了Thread 的run 方法却要调用start 方法才能启动它呢?也许看到这里你明白我想要说什么了,不过你可以假装暂时不知道,因为我后面还要进一步说明哦

上面的代码是一个不折不扣,如假包换的模板模式也就是 template 模式,是最常用也是最简单的一种设计模式,将程序的运行逻辑以及算法逻辑交由父类进行管理,子类只需要实现其中功能模块即可,但是上面有很多设计上的小技巧,需要作为重点的掌握和体会

为什么实现模块的抽象方法都是 protected 的呢?

因为我们不想让调用者关注到我们实现的细节,这也是面向对象思想封装的一个体现;

为什么 display 方法是不可继承的呢?

因为算法一旦确定就不允许更改,更改也只允许算法的所有者也就是他的主人更改,如果调用者都可通过继承进行修改,那么算法将没有严谨性可言;

2.1.3 Thread 中的 Template Desgin

已经说到这里了,想必各位已经很清楚为什么调用 start 才算是对线程的真正启动,才算是对 run 方法的线程级别调用?先别急着下结论,我们以事实说话,打开 JDK 源码我们一探究竟,看看真的是否是我们所说的那样呢?

打开代码发现 start 代码中调用了 JNI 函数start0,他就用到了模板模式;读者可以自己查看源码看看;

2.1.4 多线程编程

其实多线程的代码我们在前面已经演示过了,我们创建了一个线程,并且在 main 方法中进行了运行,其实 main 方法是一个线程,创建的线程也是一个线程,因此这就是多线程, 也不要想得太复杂,我们现在的目的就是两个代码逻辑块想要独立运行,交替执行;为了能更加的说明问题,我们重复上面的代码只不过这次比较具体一些

代码片段如下:

我们的目的就是想要做到程序中有两个独立的运行单元同时运行,体现的执行结果是交替输出

package com.wenhuisoft.chapter;class ThreadTest extends Thread{private final static int DEFAULT_VALUE = 100;private int maxValue = 0;private String threadName = "";public ThreadTest(String threadName){this(threadName,DEFAULT_VALUE);}public ThreadTest(String threadName, int defaultValue){this.maxValue = defaultValue;this.threadName = threadName;}@Overridepublic void run(){int i = 0;while(i<maxValue){i++;System.out.println("Thread:"+threadName+":"+i);}}}public class MultThreadDemo{public static void main(String[] args){ThreadTest t1 = new ThreadTest("t1"); ThreadTest t2 = new ThreadTest("t2",200); t1.start();t2.start();}}

为了节省篇幅,我将出现交替运行的地方粘贴出来即可

Thread:t2:75 Thread:t1:76 Thread:t2:76 Thread:t1:77Thread:t2:77

上面的程序就是多线程程序,上面的线程数有多少个呢?答案显而易见是三个,因为我们还有一个主线程在运行;

2.2 线程的状态

线程有他自己的状态,也就是我们所说的生命周期,简言之就是现在线程是个什么情况, 其实粗粒度的划分线程大致上有四个基本状态,当然还可以进行进一步的细化,我们在本章

中将会重点讨论这个问题,并且会以图表的方式进行讲述;

通过上面的图示我们可以大致看到,线程有四个基本的状态,那就是:

初始状态(被创建) 运行状态冻结状态终止状态(死亡)

本节我们的主要任务就是说清楚这四种状态是如何切换,以及他们中间的一些细节知识

2.2.1 线程的初始化

线程的初始化状态就是我们所说的创建了一个线程,也就是说实例化了一个 Thread 的子类,就等着被 start,初始化状态应该是很容易理解的状态;

2.2.2 线程的运行状态

线程的运行状态就是我们当创建完线程之后,显式的调用了 start 方法,此时线程就处于运行状态,可是实际是这样的么?这就要看 CPU 的脸色了,因此我刚才的说法只能说对了一半,但是不够严谨,线程被 start 之后并不一定会马上运行,因此还有一个中间状态叫做临时状态我之所以没有在上图中画,是因为我觉得这个状态可以不用太多的关注,所谓临时状态就是指,在 CPU 的执行队列当中,等待 CPU 轮询进行执行,说白了就是在等待获取执行权;

2.2.3 线程的冻结状态

所谓线程的冻结状态就是,线程被调用了 sleep 方法或者调用了wait 方法之后,放弃了CPU 的执行权,根据上图的箭头可以看到这个时候的线程能够继续回到运行状态,也就是说重新获取了 CPU 的执行权,当然它也可以直接到死亡状态,比如被中断,或者出现异常;

2.2.4 线程的死亡状态

线程在什么情况下能够到死亡状态呢?第一种是出现了致命的异常导致线程被死亡,另外一种是线程的执行逻辑执行完毕,线程也就正常死亡;死亡后的线程不可能再回到任何一个状态;

2.2.5 深入探讨

线程被 start 了为什么不能严格认为是运行状态呢?

因为 CPU 有一个执行权的问题,也就是说线程被start 之后只具备运行资格,但未必获

取到了执行权,因此不能严格认定他为运行状态;

线程冻结之后为什么还能够回到运行状态呢?

因为线程冻结之后其实他并没有死亡,他只是放弃了运行权,并且他已经没有运行资格

了,只有在解冻之后他才有可能获取运行资格,然后获取执行权;

线程的这几种状态是如何切换的呢?

1、 初始化状态只能到运行状态;

2、 运行状态能到冻结状态也能到死亡状态;

3、 冻结状态能到运行状态也能到死亡状态;

4、 死亡状态只能接受死亡的事实;

2.3 通过实现 Runnable 接口创建线程

为了能更好的引出为什么要继承 Runnable 接口来创建线程,我们将通过一个实例穿插其中,然后不断的推演,最终将 Runnable 接口引出来,并且还要说明其中的一个设计模式那就是策略模式;

需求描述:

我们假设银行有好几个柜台,每个柜台前面都有一个叫号机,从 0 号一直开始叫号,去过银行或者移动营业厅的朋友们肯定都有这样的经历,那么我们将通过程序来实现一个,然后将我们要说的重点引申出来;

2.3.1 银行排队叫号程序第一版

为了能看清输出的内容,我们假设只能叫号到 50,并且我们只开启 3 个窗口,其余的窗口都暂时休息,看了上面的知识点,您可能会想到我们开辟三个线程同时做这一件事情就可以了,如果能想到这一点,说明上面那么多的文字描述起到了作用,好了,我就揣摩着初学者的心里开始形成第一个版本的售票程序;

package com.wenhuisoft.chapter2;class TicketWindow extends Thread{int max_value = 0;//最大的号码@Overridepublic void run(){while(true){if(max_value>50){break;}System.out.println(currentThread().getName()+":"+max_value++);}}}public class Bank{public static void main(String[] args){TicketWindow t1 = new TicketWindow(); TicketWindow t2 = new TicketWindow(); TicketWindow t3 = new TicketWindow();t1.start();t2.start();t3.start();}}

好了,程序快速的写完了,但是当我们运行的时候发现,好几个线程叫到的号码是相同的,如果这种事情真的发生在银行营业厅,很容易引起混乱,输出信息如下所示:

Thread-0:4 Thread-2:4 Thread-0:5Thread-2:5

其中第一个窗口和第三个窗口都叫了 4 号和 5 号,这样肯定是不行的;虽然我们实现了多线程并发工作,但是这样的程序是相当危险的;

2.3.2 银行排队叫号程序第二版

上面的代码虽然实现了我们所谓的同时叫号程序,但是牵扯出来了一个问题,那就是多个窗口有可能同时叫道一个号码,带着这个问题我们来分析一下,然后相处解决方案,因为我们实例化了四个 TicketWindow 线程,他的成员变量 max_value 都会被分别创建,也就是各自执行各自的逻辑单元,互不相干,那么我们就想到了我们为何不有一个独一份的数据让

他们几个线程去分享操作呢?因此我们想到了静态,于是我们将代码稍作改动

Static int max_value = 0;

执行后发现的确我们的程序再也没有出现过叫同一个号码的情况,运行后的结果如下所

示:

Thread-0:45 Thread-1:46 Thread-2:47 Thread-0:48 Thread-1:49Thread-2:50

上述代码虽然实现了我们预期的功能,但是static 不是推荐使用的解决方案,因为他的生命周期实在太长了(伴随着 JVM 的销毁而结束)

好的,既然我们想到了他们需要共享同样一份业务逻辑,那么我们只需要实例化一个线程类然后启动三次不就可以了嘛,我们再次尝试一下!接着修改代码,去掉 max_value 之前的 static 描述,并且只实例化一个 TicketWindow,然后 start 三次不就可以了么?但是我们运行之后我们不幸的发现,只有一个线程在运行,并且叫完了所有的号码,并且抛出了一个异常:

Exception in thread "main" java.lang.IllegalThreadStateException at java.lang.Thread.start(Thread.java:571)at com.wenhuisoft.chapter2.Bank.main(Bank.java:32)

打开 JDK 源码我们不难找到刚才的那个异常为什么会发生,但是我们要说清楚原因,为什么会出现那样的异常呢?好了,我们一个一个的来分析为什么会出现这样的事情;

为什么会出现异常呢?这个异常代表什么意思?

线程只能被启动一次,你可以理解为规定,但是我们可以很形象的来描述它,假如你的

老师叫你起立(代表启动了你这个线程),这个时候你已经处于运行状态或者说起立状态,然后你的老师又再次对你说起立起立,正常人的思维,肯定是不合理的;

那么为什么只能有一个线程运行呢?

因为当执行第二次 start 的时候主线程已经出现了异常,导致销毁或者死亡,只有正常

启动的那个线程保持了正常的状态,因此它要等到将业务逻辑全部执行结束之后才行; 注>通过实验和 JDK 源码的查看,我们总结出来一个线程实例不能被 start 两次;

2.3.3 通过 Runnable 接口实现第三版

上面所作一切都是为了引入我们本节的主人公,那就是通过另一种方式来创建线程或者启动线程,其实 Runnable 只是一个任务的接口,他并不是一个线程,他的出现是为了将线程和业务执行逻辑分离,这是我的理解,并且我认为这样的理解是对的,呵呵;

好了,言归正传,我们通过 Runnable 接口的实现完成我们的第三版程序

package com.wenhuisoft.chapter2;class TicketWindow2 implements Runnable{private int max_value = 0;public void run(){while(true){if(max_value>50) break;System.out.println(Thread.currentThread().getName()+":"+max_va lue++);}}}public class Bank2{public static void main(String[] args){TicketWindow2 tw2 = new TicketWindow2();//1 Thread t1 = new Thread(tw2);//2Thread t2 = new Thread(tw2);//3 Thread t3 = new Thread(tw2);//4t1.start();//5 t2.start();//6 t3.start();//7}}

通过这样的编写,我们在没有用到静态的情况下实现了我们的需求功能!读者可以自行运行;

代码详解,我们通过前面的讨论,如果想要实现不出现重复输出号码的现象,我们必须保证业务逻辑的代码只有一份,因此我们只实例化了一个 TicketWindow2(注释 1)

注释 2,3,4 分别实例化了 3 个线程通过构造函数 Thread(Runnable runnable);

注释 5,6,7 分别启动了三个线程;

因此我们的出了一个结论,创建线程的两种方式有如下:

成为线程的子类,也就是继承 Thread 类; 继承 Runnable,成为一个可执行任务;

2.3.4 Runnable 和 Thread 的区别

如果没有真的理解 Runnable 那么我觉得将它们两者放在一起来讨论是有必要的,如果真的理解了他们压根就没有可比性,我们本小结的内容其实都是废话,请看下面的详细说明;

Runnable 就是一个可执行任务的标识而已,仅此而已;而 Thread 才是线程所有 API 的体现;

继承了 Thread 父类就没有办法去继承其他类,而实现了 Runnable 接口也可以继承其他类并且实现其他接口,这个区别也是很多书中千篇一律提到的,其实 Java 中的对象即使继承了其他类,也可以通过再构造一个父类的方式继承很多个类,或者通过内部类的方式继承很多个类,因此这个区别个人觉得不痛不痒;

将任务执行单元和线程的执行控制区分开来,这才是引入 Runnable 最主要的目的, Thread 你就是一个线程的操作者,或者独裁者,你有Thread 的所有方法,而 Runnable 只是一个任务的标识,只有实现了它才能称之为一个任务,这也符合面向对象接口的逻辑,接口其实就是行为的规范和标识;

2.3.5 线程中的策略模式

如果认真看了 2.3.4 中第三个区别的描述,其实应该能看出本小节的企图,我们可以姑且认为Thread 是骨架,是提供功能的,而 Runnable 只是其中某个业务逻辑的一种实现罢了,

为什么说只是一种实现呢?因为业务逻辑会是很复杂,也会是千变万化的,因此我们需要对它进行高度的抽象,这样才能将具体业务逻辑与抽象分离,程序的可扩展性才能够强,该模式也是本人在编码的时候非常喜欢的一种设计思想;

下面我们就通过实例,来让读者切身体会一下策略模式都有哪些好处!

package com.wenhuisoft.chapter2;/*** 策略接口,主要是规范或者让结构程序知道如何进行调用*/interface CalcStrategy{int calc(int x,int y);}/**程序的结构,里面约束了整个程序的框架和执行的大概流程,但并未涉及到业务层面的东西只是将一个数据如何流入如何流出做了规范,只是提供了一个默认的逻辑实现@author Administrator*/class Calculator{private int x = 0;private int y = 0;private CalcStrategy strategy = null;public Calculator(int x,int y){this.x = x;this.y = y;}public Calculator(int x,int y,CalcStrategy strategy){this(x,y);this.strategy = strategy;}public int calc(int x,int y){return x+y;}/**只需关注接口,并且将接口用到的入参传递进去即可,并不关心到底具体是要如何进行业务封装@return*/public int result(){if(null!=strategy){return strategy.calc(x, y);}return calc(x, y);}}class AddStrategy implements CalcStrategy{public int calc(int x, int y){return x+y;}}class SubStrategy implements CalcStrategy{public int calc(int x, int y){return x-y;}}public class StrategyTest{public static void main(String[] args){//没有任何策略时的结果Calculator c = new Calculator(30, 24);System.out.println(c.result());//传入减法策略的结果Calculator c1 = new Calculator(10,30,new SubStrategy()); System.out.println(c1.result());//看到这里就可以看到策略模式强大了,算法可以随意设置,系统的结构并不会发生任何变化Calculator c2 = new Calculator(30, 40, new CalcStrategy(){public int calc(int x, int y){return ((x+10)-(y*3))/2;}});System.out.println(c2.result());}}

上述代码注释的部分已经很翔实了,我就不用再多说些什么了,通过上面代码的演示, 相信大家就会明白为什么需要有 Runnable 的出现,也能体会到我为什么说将 Thread 和Runnable 来进行比较本身就是一个不合适的提议,因为他们关注的东西就不是一个事情, 一个负责线程本身的功能,另外一个则专注于业务逻辑的实现,说白了 Runnable 中的 run 方法,即使不再 Thread 中使用,他在其他地方照样也能使用,并不能说他就是一个线程;

2.3.6 Runnable 接口的用法总结

通过 Runnable 接口构造线程,其实翻阅 JDK 文档就非常清楚了,这里我只简单的说一下即可

Thread(Runnable runnable) 当然还有其他的重载构造函数,其实目的都是一样的,需要将 Runnable 传递给 Thread 才能被执行到逻辑运算单元;

2.3.7 查缺补漏

线程名字的默认编号

线程的名字默认是这样命名的 thread-n(其中 n 是从 0 开始的数字)当然你也可以通过显式的方式进行设定,比如他有 setName 方法,并且有 Thread(String name)这样的构造函

数传递名字,并且有getName()方法获取名字等

线程名字的获取方式

如何获取当前运行的线程名字呢?我们知道main 函数并没有继承Thread 也就是说我们不能通过 getName 这样的 API 获取名字,那么我们应该如何获取呢,其实 Thread 类提供了一个静态方法 Thread.currentThread()就可以获取当前运行的线程,如果获取了线程那么获取他的名字应该是易如反掌的事情了;

Programming 系列丛书附录

书名

创建时间

内容简介

《Java Annotation》

2009 年

讲解 Java 中的 Annotation 技术

《Java JDBC》

2010 年

全面讲解几乎所有的 JDBC API

《IO 编程》

2011 年

讲解并且深入分析 IO 中的各个技术细节

《Ant 入门》

2009 年

讲解 Ant 的基本用法

《Java 与反射》

2009 年

讲解 Java 中的反射 API,并且对如何使用进行了

详细的分析

《Java 与 enumeration》

2012 年

全面细致的讲解枚举中的所有技术细节

《Java 多线程编程深入详解》

2012 年

全面的讲解 Java 中的多线程编程,结合作者工作和学习多年的知识,该书中涵盖了大量的技术细

这些书的电子档我也都有收集,需要的同学转发本文+关注+私信【1205】即可白嫖!

想要资料的同学一定要先关注哦,现在头条陌生人的消息是收不到的!!

标签: #java多线程写一个文件 #c语言银行排队问题