浅谈Java多线程机制
(-----文中重点信息将用红色字体凸显-----)
一、话题导入
在开始简述Java多线程机制之前,我不得不吐槽一下我国糟糕的IT界技术分享氛围和不给力的互联网技术解答深度。当一个初学java的小哥向我请教Java多线程机制相关问题时,我让他去寻求度娘的帮助,让他先学会自己尝试解决问题;但是他告诉我在网上找不到他想要的信息,我也尝试性的在网上收刮了半天,也确实找不到内容详尽、表述清晰的文献。更遗憾的是某些也许有一定参考价值的文档都需要通过非正常手段下载,比如注册、回复甚至是花钱购买,这难免会让不少人望而却步,最后不了了之。
我并不是蓄意抨击,而是希望更多的人能够向LINUX自由之父Stallman一样,学会奉献;如果大家都能够尝试去奉献,最终每个人也将更易于索取。
(以后得空将会陆续将Java各知识点归类总结,并放在CSDN个人博客中;出Java之外还考虑介绍下其他方面的内容,届时请保持关注哟^( 。。)^)
二、现实中的类似问题
假设你是某快餐店的老板,随着自己的苦心经营,终于让快餐店门庭若市、生意兴隆;为了拓展销路,你决定增加送餐上门服务,公司财务告诉你你可以为拓展此业务支配12万元,这个时候你会怎么支配这笔钱呢?
当然有很多种支配方式,并且在支配上需要考虑到人员数量、送餐范围、送餐形式等多个问题;这里我们集中讨论下送餐形式这个细节:
1)买一辆雪弗兰赛欧;
2)买15辆电瓶车;
除去员工工资等基本成本过后剩余的钱用于购买送餐工具,上面我给出了两种送餐交通工具,他们都有各自的优点:首先,雪弗兰赛欧能够达到更快的送餐速度,而且可以供应的送餐范围更广;其次,用电瓶车作为送餐交通工具可以同时为多个顾客派送,并且运送成本显然更加低廉。在这两者之间,你会作何选择呢?
显然是第二种送餐交通工具更加实用:相较之下,后者可以处理的顾客数量更多,靠后的顾客等待时间明显缩短。试想一下,如果你打了电话定了午饭,就因为你是第25个顾客,晚上六点才给你送来,你会是什么心情?
其实,快餐店老板选择多辆电瓶车进行送餐的考虑同进程选择多线程控制的思想是如出一辙的,单线程的程序往往功能非常有限,在某些特定领域甚至不能达到我们所期望的效能。例如,当你想让服务器数据能够被多个客户同时访问时,单线程将让这一设想化为泡影;单线程情况下,多个客户的需求将存入一个栈队,并且依次执行,靠后的客户很难有较好的访问体验。
Java语言提供了非常优秀的多线程支持,多线程的程序可以包含多个顺序执行流,且多个顺序执行流之间互不干扰。总的来说,使用多线程编程有如下多个优点:
1)多个线程之间可以共享内存数据;
2)多个线程是并发执行的,可以同时完成多个任务;
3)Java语言内置了多线程功能支持,从而简化了Java的多线程编程。
三、线程的创建和启动
Java使用Thread类代表线程,所有线程对象都是Thread类或者其子类的实例。创建线程的方式有三种,分别是:
1)继承Thread类创建线程;
2)实现Runnable接口创建线程;
3)使用Callable和Future创建线程。
以上三种方式均可以创建线程,不过它们各有优劣,我将在分别叙述完每一种创建线程的方式后总结概括。
3.1 继承Thread类创建线程
主要步骤为:
① 定义一个类并继承Thread类,此类中需要重写Thread类中的run()方法,这个run()方法就是多线程都需要执行的方法;整个run()方法也叫做线程执行体;
② 创建此类的实例(对象),这时就创建了线程对象;
③ 调用线程对象的start()方法来启动该线程。
举例说明:
<span style="font-size:12px;">
public class MyThread extends Thread
{
public static void main(String[] args)
{
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
m1.start();//调用start()方法来开启线程
m2.start();</span>
}
private int a;
public void run()//重写run()方法
{
for ( ; a<100 ; a++ )
{
System.out.println(getName()+"-----"+a);
//通过继承Thread类来创建线程时,可以通过getName()方法来获取线程的名称
}
}
}
</span>
上面通过一个简单的例子演示了创建线程的第一种方法(通过继承Thread类创建线程);通过运行以上代码发现有两个线程在并发执行,它们各自分别打印出0-99。由于没有对线程进行显示的命名,所以系统默认这两个线程的名称为Thread-0和Thread-1,num会跟随线程的个数依次递增。具体怎样定义线程名称,我将在后面提及。
那么在上述例子中一共有多少个线程在运行呢?答案是三个!
分别是main(主线程)、Thread-0和Thread-1;我们在多线程编程时一定不要忘记Java程序运行时默认的主线程,main()方法的方法体就是主线程的线程执行体;同理,run()方法就是新建线程的线程执行体。
PS: 其实上述例子中创建线程的代码(标红)可以简化,使用匿名对象来创建线程:
<span style="font-family:Microsoft YaHei;font-size:12px;"><span style="font-size:12px;">new MyThread().start();
new MyThread().start();
</span></span>
------------------------------------------------------------------------------------------------------------------------------------------
在程序中如果想要获取当前线程对象可以使用方法:Thread.currentThread();
如果想要返回线程的名称,则可以使用方法:getName();
故如果想要获取当前线程的名称可以使用以上二者的搭配形式:Thread.currentThread().getName();
此外,还可以通过setName(String name)方法为线程设置名字;具体操作步骤是在定义线程后用线程对象调用setName()方法:
<span style="font-family:Microsoft YaHei;font-size:12px;">MyThread m1 = new MyThread();
m1.setName("xiancheng1");</span>
如此便能将线程对象m1的名称由Thread-0改变成xiancheng1。
------------------------------------------------------------------------------------------------------------------------------------------
在讨论完设置线程名称及获取线程名称的话题后,我们来分析下变量的共享。从以上代码运行结果来看,线程Thread0和线程Thread1分别输出0-99,由此可以看出,使用继承Thread类的方法来创建线程类时,多个线程之间无法共享线程类的实例变量。
3.2 实现Runnable接口创建线程类
主要步骤为:
① 定义一个类并实现Runnable接口,重写该接口的run()方法,run()方法的方法体依旧是该线程的线程执行体;
② 创建定义的类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象;
③ 调用线程的start()方法来启动该线程。
举例说明: