Java多线程学习(二)---线程创建方式
线程创建方式
摘要:
1. 通过继承Thread类来创建并启动多线程的方式
2. 通过实现Runnable接口来创建并启动线程的方式
3. 通过实现Callable接口来创建并启动线程的方式
4. 总结Java中创建线程的方式,比较各自优势和区别
一、继承Thread类创建线程类
1.1 继承Thread类创建线程步骤
Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。Java中通过继承Thread类来创建并启动多线程的步骤如下:
01. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务
因此把run()方法称为线程执行体
02. 创建Thread子类的实例,即创建了线程对象
03. 调用线程对象的start()方法来启动该线程
1.2 继承Thread类创建线程示例
下面程序示范了通过继承Thread类来创建并启动多线程:
- // 通过继承Thread类来创建线程类
- public class MyThreadTest extends Thread {
- private int i;
- // 重写run方法,run方法的方法体就是线程执行体
- public void run() {
- for (; i < 100; i++) {
- // 当线程类继承Thread类时,直接使用this即可获取当前线程
- // Thread对象的getName()返回当前该线程的名字
- // 因此可以直接调用getName()方法返回当前线程的名
- System.out.println(getName() + "" + i);
- }
- }
- public static void main(String[] args) {
- for (int i = 0; i < 100; i++) {
- // 调用Thread的currentThread方法获取当前线程
- System.out.println(Thread.currentThread().getName() + "" + i);
- if (i == 20) {
- // 创建、并启动第一条线程
- new MyThreadTest().start();
- // 创建、并启动第二条线程
- new MyThreadTest().start();
- }
- }
- }
- }
运行部分结果:
虽然上面程序只显式地创建并启动了2个线程,但实际上程序有3个线程,即程序显式创建的2个子线程和1个主线程。前面已经提到,当Java程序开始运行后,程序至少会创建一个主线程,主线程的线程执行体不是由run()方法确定的,而是由main()方法确定的,main()方法的方法体代表主线程的线程执行体。
该程序无论被执行多少次输出的记录数是一定的,一共是300条记录。主线程会执行for循环打印100条记录,两个子线程分别打印100条记录,一共300条记录。因为i变量是MyThreadTest的实例属性,而不是局部变量,但因为程序每次创建线程对象时都需要创建一个MyThreadTest对象,所以Thread-0和Thread-1不能共享该实例属性,所以每个线程都将执行100次循环。
二、实现Runnable接口创建线程类
2.1 实现Runnable接口创建线程步骤
实现Runnable接口来创建并启动多线程的步骤如下:
01. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体
02. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象
03. 调用线程对象的start()方法来启动线程
需要注意的是:Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。
2.2实现Runnable接口创建线程示例
下面程序示范了通过实现Runnable接口创建线程步骤:
- public class MyRunnableTest implements Runnable {
- private int i;
- void print(){
- System.out.println(Thread.currentThread().getName() + "" + i);
- }
- // run方法同样是线程执行体
- public void run() {
- for (; i < 100; i++) {
- // 当线程类实现Runnable接口时,
- // 如果想获取当前线程,只能用Thread.currentThread()方法。
- print();
- }
- }
- public static void main(String[] args) {
- for (int i = 0; i < 100; i++) {
- System.out.println(Thread.currentThread().getName() + "" + i);
- if (i == 20) {
- MyRunnableTest st = new MyRunnableTest();
- // 通过new Thread(target , name)方法创建新线程
- new Thread(st, "新线程-1").start();
- new Thread(st, "新线程-2").start();
- }
- }
- }
- }
运行部分结果:
从该运行结果中我们可以看出,控制台上输出的内容是乱序的,而且每次结果不尽相同。这是因为:
01. 在这种方式下,程序所创建的Runnable对象只是线程的target,而多个线程可以共享同一个target。
02. 所以多个线程可以共享同一个线程类即线程的target类的实例属性。
03. 往控制台窗口print()输出的过程并不是多线程安全的,在一个线程输出过程中另一个线程也可以输出。
为能够保证顺序输出,我们可以对打印方法设置Synchronized,让每次只能有一个进程能够访问打印,代码如下:
- public class MyRunnableTest implements Runnable {
- private int i;
- synchronized void print(){
- System.out.println(Thread.currentThread().getName() + "" + i);
- }
- // run方法同样是线程执行体
- public void run() {
- for (; i < 100; i++) {
- // 当线程类实现Runnable接口时,
- // 如果想获取当前线程,只能用Thread.currentThread()方法。
- print();
- }
- }
- public static void main(String[] args) {
- for (int i = 0; i < 100; i++) {
- System.out.println(Thread.currentThread().getName() + "" + i);
- if (i == 20) {
- MyRunnableTest st = new MyRunnableTest();
- // 通过new Thread(target , name)方法创建新线程
- new Thread(st, "新线程-1").start();
- new Thread(st, "新线程-2").start();
- }
- }
- }
- }
运行结果:
三、使用Callable和Future创建线程
3.1 Callable和Future接口概述
3.1.1 callable接口概述
也许受此启发,从Java 5开始,Java提供了Callable接口,该接口怎么看都像是Runnable接口的增强版,Callable接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更强大。
01. call()方法可以有返回值
02. call()方法可以声明抛出异常
因此我们完全可以提供一个Callable对象作为Thread的target,而该线程的线程执行体就是该Callable对象的call()方法。问题是:Callable接口是Java 5新增的接口,而且它不是Runnable接口的子接口,所以Callable对象不能直接作为Thread的target。而且call()方法还有一 个返回值-----call()方法并不是直接调用,它是作为线程执行体被调用的。那么如何获取call()方法的返回值呢?
3.1.2 Future接口概述
Java 5提供了Future接口来代表Callable接口里call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该实现类实现了Future接口和Runnable接口可以作为Thread类的target。在Future接口里定义了如下几个公共方法来控制它关联的Callable任务:
1. boolcan cancel(boolean maylnterruptltRunning):试图取消该Future里关联的Callable任务
2. V get():返回Callable任务里call()方法的返回值。调用该方法将导致程序阻塞,必须等到子线程结束后才会得到返回值
3. V get(long timeout,TimeUnit unit):返回Callable任务里call()方法的返回值。
该方法让程序最多阻塞timeout和unit指定的时间,如果经过指定时间后Callable任务依然没有返回值,
将会抛出TimeoutExccption异常
4. boolean isCancelled():如果在Callable任务正常完成前被取消,则返回true
5. boolean isDone():妇果Callable任务已完成,则返回true
注意:Callable接口有泛型限制,Callable接口里的泛型形参类型与call()方法返回值类型相同。
3.1.3 创建并启动有返回值的线程的步骤
01. 创建Callable接口的实现类,并实现call()方法,该cal()方法将作为线程执行体,且该call()方法有返回值
02. 创建Callable实现类的实例,使用FutureTask类来包装Callable对象
该FutureTask对象封装了该Callable对象的call()方法的返回值
03. 使用FutureTask对象作为Thread对象的target创建并启动新线程
04. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
3.2使用Callable和Future创建线程示例
下面程序示范了通过实现Callable接口创建线程步骤:
- public class MyCallableTest implements Callable<Integer>{
- // 实现call方法,作为线程执行体
- public Integer call(){
- int i = 0;
- for ( ; i < 100 ; i++ ){
- System.out.println(Thread.currentThread().getName()+ "\t" + i);
- }
- // call()方法可以有返回值
- return i;
- }
- public static void main(String[] args) {
- // 创建Callable对象
- MyCallableTest myCallableTest = new MyCallableTest();
- // 使用FutureTask来包装Callable对象
- FutureTask<Integer> task = new FutureTask<Integer>(myCallableTest);
- for (int i = 0 ; i < 100 ; i++){
- System.out.println(Thread.currentThread().getName()+ " \t" + i);
- if (i == 20){
- // 实质还是以Callable对象来创建、并启动线程
- new Thread(task , "callable").start();
- }
- }
- try{
- // 获取线程返回值
- System.out.println("callable返回值:" + task.get());
- }
- catch (Exception ex){
- ex.printStackTrace();
- }
- }
- }
运行上面程序,将看到主线程和call()方法所代表的线程交替执行的情形,程序最后还会输出call()方法的返回值:
上面程序中创建Callable实现类与创建Runnable实现类并没有太大的差别,只是Callable的call()方法允许声明抛出异常, 而且允许带返回值。当主线程中当循环变量i等于20时,程序启动以FutureTask对象为target的线程。程序最后调用FutureTask对象 的get()方法来返回call()方法的返回值——该方法将导致主线程被阻塞,直到call()方法结束并返回为止。
四、总结
通过以下三种途径可以实现多线程:
01. 继承Thread类
02. 实现Runnable接口
03. 实现Callable接口
不过实现Runnable接口与实现Callable接口的方式基本相同,只是Callable接口里定义的方法有返回值,可以声明抛出异常而已。 因此可以将实现Runnable接口和实现Callable接口归为一种方式。这种方式与继承Thread方式之间的主要差别如下。
(1) 采用实现Runnable、Callable接口的方式创建多线程
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势:编程稍稍复杂,如果需要访问当前线程,则必须使用Thread.currentThread()方法。
(2) 采用继承Thread类的方式创建多线程
劣势:因为线程类已经继承了Thread类,所以不能再继承其他父类
如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】。
如果,您希望更容易地发现我的新博客,不妨点击一下左下角的【关注我】。
如果,您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是【Sunddenly】。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利
Java多线程学习(二)---线程创建方式的更多相关文章
- Java多线程学习篇——线程的开启
随着开发项目中业务功能的增加,必然某些功能会涉及到线程以及并发编程的知识点.笔者就在现在的公司接触到了很多软硬件结合和socket通讯的项目了,很多的功能运用到了串口通讯编程,串口通讯编程的安卓端就是 ...
- Java多线程学习总结--线程概述及创建线程的方式(1)
在Java开发中,多线程是很常用的,用得好的话,可以提高程序的性能. 首先先来看一下线程和进程的区别: 1,一个应用程序就是一个进程,一个进程中有一个或多个线程.一个进程至少要有一个主线程.线程可以看 ...
- Java多线程学习(三)---线程的生命周期
线程生命周期 摘要: 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态.在线程的生命周期中,它要经过新建(New).就绪(Runnable).运行(Running).阻塞 ...
- JAVA多线程学习七-线程池
为什么用线程池 1.创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率 例如: 记创建线程消耗时间T1,执行任务消耗时间T2,销毁线程消耗时间T3 如果T1+T3> ...
- Java多线程系列1 线程创建以及状态切换
我们知道线程线程有三种创建方式 1实现Runnable接口 2 继承Thread类 3使用Callable和Future接口创建线程.具体是创建Callable接口的实现类,并实现clall()方法. ...
- java多线程机制1(线程创建的两种方式)
进程:正在运行的程序.(即程序在内存中开辟了一片空间) 线程:是进程的执行单元. 一个进程至少包含了一个多个线程. 多线程是不是可以提高效率:多线程可以合理的利用系统的资源,提高效率是相对的.因为cp ...
- JAVA多线程学习十一-线程锁技术
前面我们讲到了synchronized:那么这节就来将lock的功效. 一.locks相关类 锁相关的类都在包java.util.concurrent.locks下,有以下类和接口: |---Abst ...
- Java多线程学习之线程池源码详解
0.使用线程池的必要性 在生产环境中,如果为每个任务分配一个线程,会造成许多问题: 线程生命周期的开销非常高.线程的创建和销毁都要付出代价.比如,线程的创建需要时间,延迟处理请求.如果请求的到达率非常 ...
- java多线程学习二
声明:本篇博客是本人为了自己学习保存的心得,其内容主要是从大神——五月的仓颉的博客中学习而来,在此多谢大神五月的仓颉的分享,敬礼!如有疑问请联系博主,谢谢! 本章主要记录并讲述线程在项目中常用的方法: ...
随机推荐
- Android为TV端助力 不需要Socket的跨进程推送消息AIDL!
上篇介绍了跨进程实时通讯http://www.cnblogs.com/xiaoxiaing/p/5818161.html 但是他有个缺点就是服务端无法推送消息给客户端,今天这篇文章主要说的就是服务器推 ...
- Testlink1.9.17使用方法(第九章 测试结果分析)
第九章 测试结果分析 QQ交流群:585499566 TestLink根据测试过程中记录的数据,提供了较为丰富的度量统计功能,可以直观的得到测试管理过程中需要进行分析和总结的数据.点击首页横向导航栏中 ...
- Java:字节流和字符流(输入流和输出流)
本文内容: 什么是流 字节流 字符流 首发日期:2018-07-24 什么是流 流是个抽象的概念,是对输入输出设备的抽象,输入流可以看作一个输入通道,输出流可以看作一个输出通道. 输入流是相对程序而言 ...
- Android 官方DEMO BasicNetworking
本示例演示如何使用Android API检查网络连接. Demo下载地址:https://github.com/googlesamples/android-BasicNetworking/#readm ...
- int,int32_t,int64_t
一.数据类型特别是int相关的类型在不同位数机器的平台下长度不同.C99标准并不规定具体数据类型的长度大小,只规定级别.作下比较: 16位平台 char 1个字节8位short ...
- C#-异常处理(十四)
概念 异常处理是指程序在运行过程中,发生错误会导致程序退出,这种错误,就叫做异常 但并不是所有的错误都是异常 而处理这种错误,称为异常处理 异常处理实际是不断去发掘异常.修改异常,使程序更稳定 异常处 ...
- c/c++ vector,map,set,智能指针,综合运用的小例子
标准库,智能指针,综合运用的小例子 功能说明:查询单词在文件中出现的次数,如果在同一行出现多次,只算一次. 比如查询单词:你好 输出的结果: 你好 出现了:2次 (行号 2)xxxxxxx 你好 (行 ...
- Python 标示符和关键字
标示符 开发人员在程序中自定义的一些符号和名称.标示符是自己定义的,如变量名 .函数名等 标示符的规则 标示符由字母.下划线和数字组成,且数字不能开头 注:python中的标识符是区分大小写的 命 ...
- 用好lua+unity,让性能飞起来——lua与c#交互篇
前言 在看了uwa之前发布的<Unity项目常见Lua解决方案性能比较>,决定动手写一篇关于lua+unity方案的性能优化文. 整合lua是目前最强大的unity热更新方案,毕竟这是唯一 ...
- python 爬虫 requests+BeautifulSoup 爬取巨潮资讯公司概况代码实例
第一次写一个算是比较完整的爬虫,自我感觉极差啊,代码low,效率差,也没有保存到本地文件或者数据库,强行使用了一波多线程导致数据顺序发生了变化... 贴在这里,引以为戒吧. # -*- coding: ...