一转眼作为一名Java开发者已经四年多时间了,说长不长说短不短,对于java的感情还是比较深的,主要嘛毕竟它给了我饭吃。哈哈,开个玩笑。今天我想借此机会来和大家聊聊Java多线程。文中若有错误还请各位小伙伴及时指出。
Java是一种跨平台,适合于分布式计算环境的面向对象编程语言。关于Java的优点,我想每个刚接触Java的朋友,都会听过你的Java启蒙老师说过这么一句话:“一次编译,到处运行”。这个“到处运行”是说任何平台上只要安装了JRE,就可以运行已经编译过的(不管是什么环境编译的)Java程序。
既然Java这么厉害,那么想要知道为什么厉害肯定是要知道他的优点嘛。那么我们就去体验下Java的高级特性。Java主要高级特性有1.线程2.IO3.类集4.反射(排名不分先后)等等。今天我想借此机会来和大家聊聊Java多线程。文中若有错误还请各位小伙伴及时指出。
了解一样东西,我时常会关心的问题基本和刘姥姥进大观园一样。二点1.它是什么?(多线程是啥?)2.它可以做什么?(多线程在什么样的场景下使用?怎么去用?)
它是什么?
1.1进程和线程的概念
进程:运行中的应用程序称为进程,拥有系统资源(cpu、内存)。
线程:进程中的一段代码,一个进程中可以有多段代码。本身不拥有资源(共享所在进程的资源)。举个例子:假如我们都是工地搬砖工,我们每天都要不停的把砖搬上卡车(排队依次放砖),我们排队放砖(循环的排队,一直排下去,直到我不想干了离职,退出),每个搬砖工都有机会到达队伍的最前端把砖头放入卡车,这个就好比是线程,都有机会被程序执行。但是线程真正起作用的时候,就是我们在队伍的把砖头放入卡车,这一段时间,这是线程真正执行的阶段。
在java中,程序入口被自动创建为主线程,在主线程中可以创建多个子线程。
两者的区别1、是否占有资源问题。2、创建或撤销一个进程所需要的开销比创建或撤销一个线程所需要的开销大。3、进程为重量级组件,线程为轻量级组件
多进程:在操作系统中能同时运行多个任务(程序)。多线程:在同一应用程序中有多个功能流同时执行。
1.2线程的主要特点
①、不能以一个文件名的方式独立存在在磁盘中;②、不能单独执行,只有在进程启动后才可启动;③、线程可以共享进程相同的内存(代码与数据)。既然可以共享相同内存,这边就会有个数据一致性的问题。
1.3线程的主要用途
①、利用它可以完成重复性的工作(如每天闹钟时间的提醒)。②、从事一次性较费时的初始化工作(如网络连接、Web服务中加载一些菜单权限等)。③、并发执行的运行效果(一个进程多个线程)以实现更复杂的功能
1.4、多线程(多个线程同时运行)程序的主要优点
①、可以减轻系统性能方面的瓶颈,因为可以并行操作;②、提高CPU的处理器的效率,在多线程中,通过优先级管理,可以使重要的程序优先操作,提高了任务管理的灵活性;另一方面,在多CPU系统中,可以把不同的线程在不同的CPU中执行,真正做到同时处理多任务。
以上说明了线程的基本概念。那么接下来就用代码来说明几个小例子看下多线程在什么样的场景下使用?怎么去用?
它可以做什么?
java中创建一个线程有两种方式:
1、继承Thread类,重写run()方法,然后直接new这个对象的实例,创建一个线程的实例。然后调用start()方法启动线程。2、实现Runnable接口,重写run()方法,然后调用newThread(runnable)的方式创建一个线程,然后调用start()方法启动线程。
对于这两种方式的区别是:
继承Thread:线程代码存放Thread子类run方法中。实现Runnable,线程代码存在接口的子类的run方法。实现Runnable接口相对于继承Thread类来说,有如下的显著优势:
1.适合多个相同代码的线程去处理同一个资源的情况2.可以避免由于java的单继承特性带来的局限3.增强了程序的健壮性,代码能够被多个线程共享,代码与数据时独立的
看个有趣的例子,对比如下:
场景:某建筑工地包工头新进一批砖头有15W块,要从A地点搬到B地点。假设工地上有三个工人(看做线程哦),每天工人推板车拉砖的次数是5次,每次一车假设可以拉1W。包工头很人性化平均分道每个人头上5W砖。我们来看下非常有趣的Java代码。
A.继承Thread方式Code:
//新建一个搬砖仔的线程publicclassBanZhuanThreadextendsThread{//工人名字privateStringname;
publicBanZhuanThread(Stringname){this.name=name;}//砖头块数5W老板分的很均匀,人很好!privateintbricks=5;
Overridepublicvoidrun(){for(inti=0;i5;i++){System.out.println(name+"正在搬砖!剩余砖头"+bricks--+"万");if(i==4){System.out.println("我是"+name+",活干完了,下班");}}}}//新建三个工人去搬砖,分别新建三个工人线程大强,小强,中强去搬砖
publicclassTestThread{publicstaticvoidmain(String[]args){ThreadcatThread=newBanZhuanThread("小强");catThread.start();
ThreadcatThread2=newBanZhuanThread("大强");catThread2.start();
ThreadcatThread3=newBanZhuanThread("中强");catThread3.start();
}}
//结果输出大强正在搬砖!剩余砖头5万大强正在搬砖!剩余砖头4万大强正在搬砖!剩余砖头3万大强正在搬砖!剩余砖头2万大强正在搬砖!剩余砖头1万我是大强,活干完了,下班中强正在搬砖!剩余砖头5万中强正在搬砖!剩余砖头4万中强正在搬砖!剩余砖头3万中强正在搬砖!剩余砖头2万中强正在搬砖!剩余砖头1万我是中强,活干完了,下班小强正在搬砖!剩余砖头5万小强正在搬砖!剩余砖头4万小强正在搬砖!剩余砖头3万小强正在搬砖!剩余砖头2万小强正在搬砖!剩余砖头1万我是小强,活干完了,下班
B.实现Runnable接口方式Code:
//新建一个搬砖仔的线程publicclassBanZhuanRunnableimplementsRunnable{
//砖头总块数15W!privateintbricks=15;
Overridepublicvoidrun(){for(inti=0;i5;i++){System.out.println(Thread.currentThread().getName()+"正在搬砖!剩余砖头"+bricks--+"万");if(i==4){System.out.println("我是"+Thread.currentThread().getName()+",活干完了,下班");}}}}//新建三个工人去搬砖,分别新建三个工人线程大强,小强,中强去搬砖publicclassTestThread{publicstaticvoidmain(String[]args){
RunnablecatThread=newBanZhuanRunnable();newThread(catThread,"小强").start();newThread(catThread,"大强").start();newThread(catThread,"中强").start();}}
//结果输出小强正在搬砖!剩余砖头14万中强正在搬砖!剩余砖头13万中强正在搬砖!剩余砖头11万中强正在搬砖!剩余砖头10万中强正在搬砖!剩余砖头9万中强正在搬砖!剩余砖头8万我是中强,活干完了,下班大强正在搬砖!剩余砖头15万小强正在搬砖!剩余砖头12万小强正在搬砖!剩余砖头6万小强正在搬砖!剩余砖头5万小强正在搬砖!剩余砖头4万我是小强,活干完了,下班大强正在搬砖!剩余砖头7万大强正在搬砖!剩余砖头3万大强正在搬砖!剩余砖头2万大强正在搬砖!剩余砖头1万我是大强,活干完了,下班
看完以上代码我们可以发现在实现Runnable接口方式Code中验证了适合多个相同代码的线程去处理同一个资源(砖块)的情况。而不是像继承Thread方式Code中那样先分割。
PS:既然是共享的资源变量,那么如Runnable接口方式Code中的privateintbricks=15;就会有线程安全问题。所以如果在数据量大的情况Runnable接口方式Code代码是有一定问题的。但是从中已经可以看出他们之间的区别。如果看完还是没感觉自己敲一把体验下那个包工头的例子。
总结
由于篇幅有限,上面只是简单介绍了下Java中怎么创建线程以及基本的使用,但是已经可以体会到多线程的乐趣所在。上述列子也反映出了多线程过程中对共享资源的线程安全问题。解决这种问题就会引出锁的感念,以及多线程下怎么性能的优化。
最后说下本人对于多线程使用场景的一些经验。
1.如果你的一个大任务可以分成多个独立的并且互不影响的并且重复度极高子任务的话。那么大胆用多线程去做吧。控制好内存中线程数等问题。2.特别耗时的操作,如备份数据库,可以开个线程执行备份,然后执行返回,前台不断向后台询问线程执行状。3.需要异步处理的时候,需要使用多线程。4.对于多线程中,执行失败的时候尽可能要有补偿机制。哪怕是写日志记录也可以。
说着说着就想不到了。。。。。先这样吧。-.-!