Java多线程的11种创建方式以及纠正网上流传很久的一个谬误
创建线程比较传统的方式是继承Thread类和实现Runnable,也可以用内部类,Lambda表达式,线程池,FutureTask等。
经常面试会问到继承Thread类和实现Runnable的区别,然后网上会流传如下这样的说法,这是错误的。
流传很久的错误说法:
这个说法是举一个火车票售票的例子,大致意思是说实现Runnable接口可以实现多继承,这一点说的是正确的,但是错误的是下面的,那些例子会说实现Runnable接口的多线程可以实现共享,而继承Thread类的线程是不会共享的。其实之所以造成他们的说法看起来对是刻意在继承Thread类的时候故意新建几个线程,而成员变量又不是静态的自然是不能共享的。
下面就是那个流传很广的关于继承Thread类和实现Runnable的两种方式的“区别”的来源:
参考类似此文《创建线程的两种方式比较Thread VS Runnable》
说明:上面截图中的是片面的,然后很多培训机构和很多博主还引用这个,所以有必要澄清下这个问题。
正确的说法是:继承Thread类和实现Runnable的最本质的区别是继承接口可以实现多继承。继承Thread类的一样可以实现共享。
先看Thread类和Runnable接口的关系
可以看到 Runnable接口就一个run方法,然后Thread类实现了这个run方法,同时自己又实现了很多其他方法。
1.继承Thread类和重写run()方法
public class MyThread extends Thread{private String name;private int i = 0;public MyThread(String name){this.name = name;}@Overridepublic void run() {i++;System.out.println(name+" i计数为:"+i+" "+Thread.currentThread().getName());}
}
public class MyThreadTest {public static void main(String[] args) {MyThread mt1 = new MyThread("线程");Thread t1 = new Thread(mt1);Thread t2 = new Thread(mt1);Thread t3 = new Thread(mt1);t1.start();t2.start();t3.start();}
}
说明:这样就实现了所谓继承的方式不能多个线程处理同一个资源的情况。
你可以自定义线程的name,这样就不会是Thread-0,1,2,3这样的系统生成的name了。
再修改代码:
public class MyThreadTest {public static void main(String[] args) {MyThread t1 = new MyThread("线程1");MyThread t2 = new MyThread("线程2");MyThread t3 = new MyThread("线程3");t1.start();t2.start();t3.start();}
}
这次每个线程之间就不会共享数据,也就是那些谬误中的用法。
2.实现Runnable接口
public class MyThreadRunnable implements Runnable{private String name;private int i = 0;public MyThreadRunnable(String name){this.name = name;}@Overridepublic void run() {i++;System.out.println(name+" i计数为:"+i+" "+Thread.currentThread().getName());}
}
public class MyThreadTest {public static void main(String[] args) {//MyThread t1 = new MyThread("线程1");//MyThread t2 = new MyThread("线程2");//MyThread t3 = new MyThread("线程3");MyThreadRunnable mt=new MyThreadRunnable("线程1");Thread t1 = new Thread(mt);Thread t2 = new Thread(mt);Thread t3 = new Thread(mt);t1.start();t2.start();t3.start();}
}
实现Runnable接口可以共享变量。
再把代码改一改:
public class MyThreadTest {public static void main(String[] args) {//MyThread t1 = new MyThread("线程1");//MyThread t2 = new MyThread("线程2");//MyThread t3 = new MyThread("线程3");MyThreadRunnable mt1=new MyThreadRunnable("线程1");MyThreadRunnable mt2=new MyThreadRunnable("线程2");MyThreadRunnable mt3=new MyThreadRunnable("线程3");Thread t1 = new Thread(mt1);Thread t2 = new Thread(mt2);Thread t3 = new Thread(mt3);t1.start();t2.start();t3.start();}
}
这次就不会共享变量了。
所以这样就证明了共享变量和继承Thread类和实现Runnable并无关系!
3.实现线程创建的两种简洁方式(匿名内部类+Lambda表达式)
这种方式适合于创建启动线程次数较少的环境,一般书写更加简便。
//方式1:相当于继承了Thread类,作为子类重写run()实现new Thread() {public void run() {System.out.println("匿名内部类创建线程方式1..." + Thread.currentThread().getName());}}.start();//方式2:实现Runnable,Runnable作为匿名内部类new Thread(new Runnable() {public void run() {System.out.println("匿名内部类创建线程方式2..." + Thread.currentThread().getName());}}).start();//方式3:Lambda表达式创建线程new Thread(() -> {System.out.println("Lambda表达式创建线程方式..." + Thread.currentThread().getName());}).start();
如果需要自定义线程名字可以修改如下:
//方式3:Lambda表达式创建线程new Thread(() -> {System.out.println("Lambda表达式创建线程方式..." + Thread.currentThread().getName());},"线程1").start();
4.实现线程的线程池方式
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadPool {public static void main(String[] args) {ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);for (int i = 0; i < 10; i++) {final int index = i;fixedThreadPool.execute(new Runnable() {public void run() {try {System.out.println(index+ " "+Thread.currentThread().getName()+" "+DateUtil.getNowTimeString());Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}});}fixedThreadPool.shutdown();}
}
这个帖子写的很好,还有其他几种模式: 《java常用的几种线程池比较》
newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool
创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。
newSingleThreadExecutor
创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。
newScheduleThreadPool
创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。
5.实现异步的Future,FutureTask,CompletableFuture方式
本质还是线程,因为Java目前语言层面没有协程,需要三方类库或者修改JVM才可以,
参考本人另一帖:《异步编程原理以及Java实现》,具体源码分析都有本质还是调用class Executors,只不过是这种方式可以回调而已。原因是继承和实现了Runnable接口这2种方式创建线程都有一个缺陷就是:在执行完任务之后无法获取执行结果。
自从Java 1.5开始,就提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果。
虽然 Future 以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果。阻塞的方式显然和我们的异步编程的初衷相违背,轮询的方式又会耗费无谓的 CPU 资源,而且也不能及时地得到计算结果。
在Java8中,CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合 CompletableFuture 的方法。
《CompletableFuture基本用法》
6.定时器方式
这种方式不是为了实现线程,但是他确实是起了一个线程
import java.util.Timer;
import java.util.TimerTask;public class TimerTest {public static void main(String[] args) {Timer timer = new Timer();timer.schedule(newTimerTask() {@Overridepublic void run() {System.out.println("定时任务延迟0(即立刻执行),每隔1000ms执行一次"+ " "+Thread.currentThread().getName()+" "+ DateUtil.getNowTimeString());}}, 0, 1000);}
}
public abstract class TimerTask implements Runnable
从类图可以看出TimerTask实现了Runnable接口的run()方法。
详细分析可以参考《Java 定时器 Timer 源码分析和使用建议》:
Timer 可以按计划执行重复的任务或者定时执行指定任务,这是因为 Timer 内部利用了一个后台线程 TimerThread 有计划地执行指定任务。
Timer:是一个实用工具类,该类用来调度一个线程(schedule a thread),使它可以在将来某一时刻执行。 Java 的 Timer 类可以调度一个任务运行一次或定期循环运行。 Timer tasks should complete quickly. 即定时器中的操作要尽可能花费短的时间。
TimerTask:一个抽象类,它实现了 Runnable 接口。我们需要扩展该类以便创建自己的 TimerTask ,这个 TimerTask 可以被 Timer 调度。
一个 Timer 对象对应的是单个后台线程,其内部维护了一个 TaskQueue,用于顺序执行计时器任务 TimeTask 。
7.Spring异步任务支持
@EnableAsync和@Async开始异步任务支持
Spring通过任务执行器(TaskExecutor)来实现多线程和并发编程。使用ThreadPoolTaskExecutor可实现一个基于线程池的TaskExecutor.在开发中实现异步任务,我们可以在配置类中添加@EnableAsync开始对异步任务的支持,并在相应的方法中使用@Async注解来声明一个异步任务。
详细参考《@EnableAsync和@Async开始异步任务支持》
8.可以用并行流创建线程
import java.util.Arrays;
import java.util.stream.Collectors;public class StreamParallel {public static void main(String args[]) {for (int i = 0; i < 1000000; i++) {Arrays.asList(1, 2, 3, 4, 5, 6, 7, 9, 8, 0, 1).stream().parallel().collect(Collectors.groupingBy(x -> x % 10)).forEach((x, y) -> System.out.println(x + ":" + y));}}
}
可以看到默认的parallel计算启动了三个线程进行并行。
参考《Java8 Stream 并行计算实现的原理》
Java 并发编程:线程间的协作(wait/notify/sleep/yield/join)
相关文章:
【CTF】实验吧 古典密码
一共是35个字符分成5*7或者7*5 最终选择5行7列 首先变动第一行的位置,然后根据第一行变动的位置,依次变动下面的行 OCU{CFT ELXOUYD ECTNGAH OHRNFIE NM}IOTA CTF{COU LTYOUEX CHANGET HEINFOR MATION} CTF{COULTYOUEXCHANGETHEINFORMATION}

对比React Native、dcloud、LuaView三个框架技术(内部)
转载自:http://www.jianshu.com/p/ee1cdb33db8d主要对比React Native和5SDK(就是dcloud的SDK)两个: 开发语言:三个都是用其他语言来统一开发IOS、android应用的框架技术,其中,React Native是使用…

spring boot 临时文件过期
2019独角兽企业重金招聘Python工程师标准>>> 第一种方案:-Djava.io.tmpdir /xxx 第二种方案: 线上的系统中不能上传文件了,出现如下错误: org.springframework.web.multipart.MultipartException: Could not parse mu…

ASP.NET MVC+Bootstrap个人博客之打造清新分页Helper(三)
0. 没有找到一款中意的分页插件,又不想使用现成的(丑到爆),所以自己动手造一个吧 先看下效果(其实也不咋滴...): 有点另类,分页直接是在后台拼接好html,然后发送到前台的: 1. 分页容器: <di…
支撑Java框架的基础技术:泛型,反射,动态代理,cglib
以Spring为例要想看明白他的源码需要彻底理解Java的一些基础技术泛型,反射同时对于一些高级技术例如动态代理,cglib和字节码技术也需要掌握,下面就按章节来一一说清楚这些技术的核心部分,最后手写一个简单的Spring框架。 一.静态代…
【CTF】实验吧 困在栅栏里的凯撒
题目先提到栅栏,再提到凯撒,按照顺序先栅栏解码,再凯撒解码。 一般密码的开头不是flag就是key或者ctf 所以选择“6栏”,在进行凯撒解码 在所有组合中,发现CTF即为flag
经典算法书籍推荐以及算法书排行【算法四库全书】
经典算法书籍推荐以及算法书排行【算法四库全书】 作者:霞落满天 https://linuxstyle.blog.csdn.net/ https://blog.csdn.net/21aspnet 行文方式:类似《四库全书》截取经典算法书目录和精华篇章 版权说明:本文于2019年5月5日首发于CS…
【CTF】实验吧 Fair-Play
它的标题就是题解的提示:Play-Fair Playfair解密算法首先将密钥填写在一个5*5的矩阵中(去Q留Z),矩阵中其它未用到的字母按顺序填在矩阵剩余位置中,根据替换矩阵由密文得到明文。 对密文解密规则如下: 1 若c…

【DAY23】JVM与反射的学习笔记
JVM:-----------------1.JVM: java virtual machine.2.class file *.class3.ClassLoader4.runtime data area运行时数据区。1.Method area : 方法区.(shared)供所有线程共享.2.heap(shared):供所有线程共享.3.java stack(栈区)独占的。4.native method stack(本地方法栈)独占5.…

BZOJ2281:[SDOI2011]黑白棋(博弈论,组合数学,DP)
Description 小A和小B又想到了一个新的游戏。这个游戏是在一个1*n的棋盘上进行的,棋盘上有k个棋子,一半是黑色,一半是白色。最左边是白色棋子,最右边是黑色棋子,相邻的棋子颜色不同。小A可以移动白色棋子,小…

高性能微服务架构设计模式@霞落满天
高性能微服务架构设计模式 主讲:霞落满天 现在企业开发都是微服务架构,但是有很多问题,比如分布式定义,分布式的微服务怎么拆分,什么时候拆分,怎么做到高性能,中台怎么设计,读写分…

【数据结构】顺序栈的实现(C语言)
栈的基本概念及其描述 栈是一种特殊的线性表,规定它的插入运算和删除运算均在线性表的同一端进行,进行插入操作和删除操作的那一端称为栈顶,另一端称为栈底。 栈的插入操作和删除操作分别称为进栈和出栈。 FILO(First In Last …

iOS绘制图片与文字
2019独角兽企业重金招聘Python工程师标准>>> #####绘制图片与文字 #####1.绘制图片,直接代码说明 加载图片 #pragma mark - 小黄人 -(void) drawImage:(CGRect) rect{UIImage *image[UIImage imageNamed:"黄人"];//图片有可能显示不全…

php-fpm慢执行日志
vim /usr/local/php-fpm/etc/php-fpm.d/www.conf//加入如下内容request_slowlog_timeout 1slowlog /usr/local/php-fpm/var/log/www-slow.log 测试:/usr/local/php-fpm/sbin/php-fpm -t/etc/init.d/php-fpm reloadls ../../var/log/ //生成日志php-fpm.log www-sl…

spring springboot springcloud常用注解
SpringBootApplication 组合注解,用在启动类上,源码: Retention(RetentionPolicy.RUNTIME) SpringBootConfiguration EnableAutoConfiguration ComponentScan public interface SpringBootApplication SpringBootConfiguration Configurat…

解决eclipse ctrl+鼠标左键不能用
选择【Window】菜单 Preferences ——>General——>Editors——>Text Editors——>Hyperlinking 把勾都点上,然后确定KEY 值为 crtl

【数据结构】顺序队列的实现(C语言)
队列的基本概念及其描述 队列是一种特殊的线性表,它的特殊性在于队列的插入和删除操作分别在表的两端进行。 插入的那一端称为队尾,删除的那一端称为队首。队列的插入操作和删除操作分别称为进队和出队。 先进先出(First In First Out&…

ethereumjs/ethereumjs-vm-2-API文档
https://github.com/ethereumjs/ethereumjs-vm/blob/master/docs/index.md vm.runBlockchain Processes blocks and adds them to the blockchain 处理区块并将其添加到区块链中 Parameters输入参数 blockchain Blockchain A blockchain that to process 一个处理的区块链cb Fu…

qt 拖拽 修改大小(二)
最近项目需要实现windows下橡皮筋的效果,所以对此做了一些了解,特此记录。 首先windows系统是支持橡皮筋效果的,需要使用win32方 法:SystemParametersInfo(SPI_SETDRAGFULLWINDOWS, showFullWindow, NULL, 0);showFullWindow是一个…

互联网大厂技术面试内幕@霞落满天
很多求职者往往并非因为技术不好,而是没有掌握面试的技巧导致不能把握机会,本课程的目的就是本课程先通过比较真实的好简历和不好的简历让大家明白自己的简历有哪些问题,事实上简历是大厂的敲门砖,非常重要,很多人得不…

【数据结构】顺序表的应用(1)(C语言)
问题: 1.将顺序表(a1,a2,…,an)重新排列以a1为界的两部分:a1前面的值均比a1小,a1后面的值均比a1大(这里假设数据元素的类型具有可比性,不妨设为整型)。 头文件与该头文件一样:【数据结构】顺序…

比特币寒冬中,你更应该关注企业区块链!
公众对区块链的认识也许限于比特币或以太坊,但很多却不知道 Hyperledger(超级账本)。Hyperledger Fabric,是由 IBM 带头发起的一个联盟链项目,2015 年末移交给 Linux 基金会,成为开源项目。Linux 基金会孵化…

JVM XMX设置多大比较好,Docke容器里该怎么设置JVM呢@无界编程
XMX是JVM的最大堆内存大小,XMS是JVM的初始堆内存大小。 不管是工作还是面试经常遇到一个问题就是XMX到底设置多大比较好? 网上的答案大多是说XMX和XMS设置为一样大,但是没有说到底XMX设置多大比较好。 如果设置为和操作系统一样大内存会怎么样? 这篇文章就带你搞清楚这…

【数据结构】顺序表的应用(2)(C语言)
问题: 2.有顺序表A和B,其元素均按从小到大的升序排列,编写一个算法,将它们合并成一个顺序表C,要求C的元素也按从小到大的升序排列。 头文件与该头文件一样:【数据结构】顺序表的实现(C语言&am…

OWA登录页面显示为英文而不是中文
-----提供AD\Exchange\Lync\Sharepoint\CRM\SC\O365等微软产品实施及外包,QQ:185426445.电话18666943750故障描述:WIN10操作系统使用IE登录OWA的时候,界面语言为英文,WIN10操作系统为中文系统,区域语言都是设置为中文&…

java B2B2C springmvc mybatis多租户电子商城系统-Spring Cloud Feign
1、什么是Feign? 愿意了解源码的朋友直接企鹅求求:二一四七七七五六三三 Feign 的英文表意为“假装,伪装,变形”, 是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,而不用…

【数据结构】顺序表的应用(3)(C语言)
问题: 已知一个顺序表中的各节点值是从大到小有序的,设计一个算法,插入一个值为x的节点,使顺序表中的节点仍然是从小到大有序的。 头文件与该头文件一样:【数据结构】顺序表的实现(C语言) #i…

从源码和内核角度分析redis和nginx以及java NIO可以支持多大的并发
有人询问我网上一篇关于“redis为什么单线程这么快”的文章,我建议他不要看了,因为redis是单进程不是单线程,后面的意见不用看了,文章质量肯定不会很好,他也说了自己看了很久源码似乎还是有些云里雾里,所以我就给他分析了为什么redis这么快,这篇主要讲epoll的实现。 从…

背景图片等比缩放的写法background-size简写法
1、背景图片或图标也可像img一样给其宽高就能指定其缩放大小了。 比如一个实际宽高36*28的图标,要缩小一半引用进来的写法就是: background:rgba(0, 0, 0, 0) url("../images/report_icon2x.png") no-repeat scroll left center / 18px 14px; …

深入了解以太坊
正在看这篇文章的你,应该是一名被区块链技术所吸引的开发者或者极客。我相信你已经理解了区块链的技术原理,并急切地想要搞清楚这项技术将为你和你的开发技术栈带来怎样的影响。 如果你需要更基础的区块链技术介绍,可以阅读比特币和以太坊的白…