线程:从模糊到清晰的要点理解

[color=blue][size=medium][align=center]线程:从模糊到清晰的要点理解[/align][/size][/color]
线程真的很难学,起码在我进行写这篇博文之前,我是这么认为的。甚至,我还对线程产生了恐惧,更别说多线程了。但是,经过我的再次学习,我对线程的认识终于算是棱角分明了。这里,我从以下几个方面来认识线程:
一、线程是什么
二、多线程是什么
三、线程的优先级是什么意思
四、线程的启动方法是start(),还是run()
五、线程的状态
[b]一、线程是什么[/b]
线程是一个程序内部的顺序控制流。在这里,我是通过将线程和进程进行比较来理解线程的。
线程和进程的区别:
(1)每个进程都有独立的代码和数据空间,进程间的切换会有较大的开销。
(2)线程可以看成是轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立的程序计数器(PC),线程切换的开销较下。
(3)多进程:在操作系统中能同时运行多个任务,即启动多个程序。
(4)多线程:在统一应用程序中有多个顺序流同时执行

确切的说进程是不可以执行的,进程是一个静态的概念,但我们所说的进程的执行指的是进程中的主线程main方法的执行,进程执行时,其实是线程的执行。Windows、Linux、Unix都是多进程多线程的操作线系统,而dos是单进程的操作系统。
VM启动时,会有一个主方法(public static void main(String[] args))所定义的线程。main方法又叫主线程。但是在实际中,一个程序中往往包含不止一个线程,即多线程。

[b]二、多线程是什么[/b]
在这里,我有必要阐述一下多线程的概念。之前我理解的多线程是在一个时间点上,在一台计算机中有多个任务在同时在执行。当然,这样理解也没错,但是,我们得注意到这样理解的一个前提,那就是这台计算机是双核或者是多CPU。如果是只有一个CPU,那我们就要换种方法来理解多线程了。
在同一个时间点上,一个CPU只支持执行一个线程,但我们所谓的多线程执行,是因为CPU的执行速度太快,而且,CPU是分块交替执行,即把每个线程划分成很多个块,执行完这个线程的某一块之后,又去执行下一个线程的某一块,一段时间过后,每一个线程都有被执行到,但是这个一段时间是人的眼睛无法衡量的,所以给人们一个错觉:多个线程是同时执行的。当然真正的多线程同时执行也是存在的,比如说你的电脑是两个CPU或者是多核,就可以多线程执行。
我们来进行下面这个测试:main方法和Thread线程的run方法各执行一段代码(for循环),看输出结果如下:
/**
* main()方法和线程start()方法测试类
* @author CHP
*
*/
public class ThreadTest {
public static void main(String[] args){
RunnerTest thr=new RunnerTest();
new Thread(thr).start();
for(int i=0;i<10;i++){
System.out.println("main()--执行:"+i);
}
}
}

class RunnerTest implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(">>RunnerTest执行:"+i);
}
}
}

输出结果为:
main()--执行:0
main()--执行:1
main()--执行:2
main()--执行:3
>>RunnerTest执行:0
main()--执行:4
main()--执行:5
main()--执行:6
main()--执行:7
main()--执行:8
>>RunnerTest执行:1
main()--执行:9
>>RunnerTest执行:2
>>RunnerTest执行:3
>>RunnerTest执行:4
>>RunnerTest执行:5
>>RunnerTest执行:6
>>RunnerTest执行:7
>>RunnerTest执行:8
>>RunnerTest执行:9
从结果中可以看出main方法这个主线程和RunnerTest这个线程是交替执行的,也就是说,当RunnerTest启动(.start())后,这个测试中有两个线程在运行,CPU在两个线程之间交替执行,执行示意图如下:
[align=center][img]http://dl.iteye.com/upload/attachment/0080/7530/91cd5997-adc3-3273-811f-7182eea3d8eb.png[/img][/align]

说到这里,我算是明白了:线程有两个主要方法run()和start()。
[b]三、线程的优先级是什么意思[/b]
在上面我解释说多线程其实是CPU对线程的分块交替执行,在此,我想说明一点,在线程中存在一个线程优先级的概念。Java提供一个线程调度器来监控程序中的启动后进入就绪状态的所有线程。线程调度器按照线程的优先级决定应调度哪一个线程来执行。因此,我们可以提出这样的疑问:如果给线程设置了优先级,是否就可以改变线程的执行顺序呢?我们看下面的例子:
public class Test {
public static void main(String[] args){
Thread thr1=new Thread(new RunnerTest1());
Thread thr2=new Thread(new RunnerTest2());
thr1.start();
thr2.start();
}
}

class RunnerTest1 implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(">>1执行:"+i);
}
}
}
class RunnerTest2 implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("----------2执行:"+i);
}
}
}

执行结果是:
>>1执行:0
----------2执行:0
----------2执行:1
>>1执行:1
>>1执行:2
>>1执行:3
>>1执行:4
>>1执行:5
>>1执行:6
>>1执行:7
>>1执行:8
>>1执行:9
----------2执行:2
----------2执行:3
----------2执行:4
----------2执行:5
----------2执行:6
----------2执行:7
----------2执行:8
----------2执行:9
经过多次测试,从输出结果我们可以会发现,线程1和线程2执行的时间间隔差不多。但是,如果提高线程1的优先级,结果就不是那么回事了,我们可以明显的看出线程1每次执行的时间长于线程2(为使结果明显,建议把循环的数值设置大一点)。也就是说线程的优先级确实可以改变线程的执行顺序。
但是,这种改变也不是绝对的。在Java中Thread.setPriority()只是应用与局部的优先级,不是在整个可能的范围内设定优先级。比如说,在我们启动线程时,可能会有多个级别的线程在运行,但是我们都希望控制鼠标的这个线程不被其他线程抢占资源。Thread.setPriority()可能会根本不做任何事,这根你的早做系统和虚拟版本有关,具体猫腻,有待研究。因此,优先级对想成运行顺序的影响并不能靠我们主观臆测来判断。

[b]四、线程的启动方法是start(),还是run()[/b]
我在想,在上面的例子中,如果直接用Runnable的实现子类对象直接去调用run()方法,让线程去执行方法体中的方法,这种方法和之前用Thread线程调用start()方法来执行run()方法体中的内容有什么区别呢?
这里我们再做一个测试:
public class ThreadTest {
public static void main(String[] args){
RunnerTest thr=new RunnerTest();
thr.run();
// new Thread(thr).start();
for(int i=0;i<100;i++){
System.out.println("main()--执行:"+i);
}
}
}

class RunnerTest implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println(">>RunnerTest执行:"+i);
}
}
}

还是刚才那个例子,唯一不同的地方是我们用Runnable的实现类对象调用线程的run()方法来启动线程,见下划线标注处。这样执行的结果如下:
>>RunnerTest执行:0
>>RunnerTest执行:1
>>RunnerTest执行:2
>>RunnerTest执行:3
>>RunnerTest执行:4
>>RunnerTest执行:5
>>RunnerTest执行:6
>>RunnerTest执行:7
>>RunnerTest执行:8
>>RunnerTest执行:9
main()--执行:0
main()--执行:1
main()--执行:2
main()--执行:3
main()--执行:4
main()--执行:5
main()--执行:6
main()--执行:7
main()--执行:8
main()--执行:9
从结果中,我们可以看出,在整个程序中,CPU是把run()方法体中的代码全部执行完了之后再去执行main()方法体中的for循环,整个执行过称是一个线性的。也就是说当CPU去执行run()方法时,main方法中的其他语句是处于等待状态的,当run()方法执行结束,main方法接收到run()方法的返回值之后,main()方法继续执行其他语句,整个执行过程如下图所示:
[align=center][img]http://dl.iteye.com/upload/attachment/0080/7532/c4e41210-3177-3791-85bd-4a70d617dc13.png[/img][/align]

所以,thr.run()是方法的调用,效果相当于,start()后,再join(millis)(等待该线程终止的最长时间为millis毫秒,即相当于合并为一个线程);并不等同于start()方法。对于start()方法,解释是:Causes this thread to begin execution; the Java Virtual Machine calls the <code>run</code> method of this thread.也就是说,start()方法是通知计算机CPU现在有一个线程要启动啦,JVM准备启动一个线程,执行run()方法,而不是由main()主线程去调用run()方法。

[b]五、线程的状态[/b]
线程分为5个状态,创建,就绪,运行,阻塞,终止

[align=center][img]http://dl.iteye.com/upload/attachment/0080/7527/a00a6c6b-3db5-3b19-bf73-6fd55721d124.png[/img][/align]

在这里我重点想讲述一下线程的终止。终止线程最直接的方法就是控制run()方法体的执行条件,因此,我们只要给run()方法体的方法加一个执行条件,我们只要让条件不满足,就可以终止一个线程。我当时在做一个线程游戏时,由于需要,我要终止一个线程,我当时使用了多种方法来实现这个功能,现在我来具体分析下这几个线程:stop(),interrupted(),yield()
1.interrupt():中断线程。这种方法虽然可以中断线程,但是很暴力,也就说,无论是线程处于运行状态还是处于睡眠状态,它都会强行中断。如果线程正在打开一个资源时,啪,线程被打断了,这就很不好;再比如说,你正在睡觉,突然一盆凉水泼在你身上,这很难受的,对于线程,它也受不了啊。所以,这不是最好的方法。
2.stop():终止线程,当使用这种方法时,会显式stop(),这表示,这个方法不推荐使用,甚至是尽量别去使用。对于这个方法我的理解是,stop()的作用是一棒子打死。简直就是interrupt()方法的暴力升级版。起码,interrupt()还会将线程交给InterruptedException去处理,而stop()是不给你喘息的机会,一刀毙命。当我们的计算机卡死时,我们会打开任务管理器,直接结束某个进程,这个时候,我们就是在使用stop()。
3.yield():暂停当前正在执行的线程。执行到某一个点的时候,yield()发挥其高风亮节的作风,把CPU让出来,让其他的线程去执行,但是,这并不是无条件让出,因此,yield还会再执行的。不算是线程终止方法。

到目前为止,线程曾是最让我头疼的一个环节。因为我根本没办法理解线程到底是怎么达到想要的执行效果的。让我头疼的是,明明我想的是应该执行这个内容,但最后输出的结果却并不一定是我想的那样。因此,我重新学习了以上几点内容,现在终于算是明朗了。不知道你对线程的理解清楚了没,加油啊!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值