前不久参加了一个IBM的面试,两个小时进两百的选择题,让我感到前所未有的挫折感,虽说拼命做了出来,但是还是感到自己的基础知识漏洞无数。其中java部分的多线程由于平时涉及较少,异常吃力,令我诧异的居然考了很多,所以我觉得有必要研究一下。希望与大家交流。
所谓“进程”(process), 是一个独立运行的程序,它有自己的地址空间。而线程是进程内部的单一控制流。因此一个进程内可以具有多个并发执行的线程。
使用并发最强制性的原因之一就是要产生能够做出响应的用户界面。一个程序需要在处理自己内部计算的同时响应用户的操作。
写一个简单的线程最简单的做法是从java.lang.Thread继承。下面是一个小例子.
public class SimpleThread extends Thread{
private int count=3;
private static int threadCount=0;
public SimpleThread() {
super("" + ++threadCount); //store the name of the thread
start();
}
public String toString() {
return "Thread" + this.getName() + ": " + count;
}
public void run() {
while(true) {
System.out.println(this);
if (--count == 0) return;
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
for (int i=0; i<3; i++)
new SimpleThread();
}
}
我的运行结果是这样的:
Thread1: 3
Thread2: 3
Thread2: 2
Thread2: 1
Thread1: 2
Thread3: 3
Thread1: 1
Thread3: 2
Thread3: 1
从上面的结果我们就可以看出线程调度的情况,当然不同的机器上结果很可能是不一样的。如果你的机器上的结果显示是线程1到线程3是按顺序完成的,很可能是由于程序中count的值太小了,以至于线程可以是被调度之前就完成了自己的运行,你可以将count值改的大一点,比如100,你就会看出明显的结果。线程是交叉调度运行的(早期的jdk经常不是切片时间的,所以很可能是线程陆续执行)。
在main()里创建了并运行了一些线程。Thread类的start()方法将为线程执行特殊的初始化操作,然后调用run()方法,开始线程的运行。
如果run()的代码改为:
1public void run() {
2 while(true) {
3 System.out.println(this);
4 if (--count == 0) return;
5 yield();
6 }
7 }
运行结果将变为:
Thread1: 3
Thread2: 3
Thread3: 3
Thread1: 2
Thread2: 2
Thread3: 2
Thread1: 1
Thread3: 1
Thread2: 1
yield()让线程让出处理器。一般来说,用yield()进行程序的调整是危险的,因为调度机制是抢占式的,它会决定它认为在最应该的时候切换占用处理器的线程。在以上的程序中如果改变toString()方法,是输出很长,它就会觉得输入输出占用了太多时间了,改换人了,所以很可能还有执行到yield()的时候,线程已经被迫让出处理器了。
同样,sleep()也不是控制线程执行顺序的好方法,他只是让线程停止执行一段时间。如果你必须控制线程的执行顺序,最好是根本不用线程,而是自己编写特定顺序彼此控制的协作子程序。
优先权
如果许多线程被阻塞等待运行,那么调度程序倾向于让优先级最高的线程运行。
下面是一个示例:
1
2public class ThreadPriority extends Thread{
3
4 private int count=3;
5 private volatile double d=0; //不要优化
6
7 public ThreadPriority(int p) {
8 this.setPriority(p);
9 this.start();
10 }
11
12 public String toString() {
13 return super.toString() + ": "+ count;
14 }
15
16 public void run() {
17 while(true) {
18 for (int i=1; i<99999; i++) {
19 d = d + (Math.PI + Math.E)/(double)i; //重体力活
20 }
21 System.out.println(this);
22 if (--count ==0)return;
23 }
24 }
25 /** *//**
26 * @param args
27 */
28 public static void main(String[] args) {
29 // TODO Auto-generated method stub
30 new ThreadPriority(Thread.MAX_PRIORITY);
31 for (int i=0; i<3; i++) {
32 new ThreadPriority(Thread.NORM_PRIORITY);
33 }
34 }
35
36}
如果现在让你猜结果,也许你会觉得是优先级最高的应该先完成,然后优先级一样的几个线程交织运行直到结束,其实我也是这么认为的,但是事与愿违,结果是这样的(我的机器):
Thread[Thread-0,10,main]: 3
Thread[Thread-1,5,main]: 3
Thread[Thread-0,10,main]: 2
Thread[Thread-1,5,main]: 2
Thread[Thread-0,10,main]: 1
Thread[Thread-1,5,main]: 1
Thread[Thread-2,5,main]: 3
Thread[Thread-3,5,main]: 3
Thread[Thread-2,5,main]: 2
Thread[Thread-3,5,main]: 2
Thread[Thread-2,5,main]: 1
Thread[Thread-3,5,main]: 1
请不要诧异,这就是多线程的困扰。这是因为,如果让优先级最高的执行完成再让其他线程运行实在是太残忍了,因为它的耗时实在无法让人容忍,所以即便他的优先级是最高也要接受组织的安排。JDK有十个优先级,但通常使用的是以下三个:
MAX_PRIORITY,
NORM_PRIOITY,
MIN_PRIORITY
分别是最大,普通,最小优先级。
后台线程
“后台”(Daemon)线程,是指程序运行的时候,在后台提供一种通用服务的线程,并且这种服务并不属于程序中不可或缺的部分。因此,当所有的非后台进程结束,程序就终止了。反过来说,只要有任何非后台线程还在运行,程序就不会终止,比如,执行main()的就是一个非后台进程。下面是一个示例:
1public class SimpleDaemon extends Thread{
2 public SimpleDaemon() {
3 this.setDaemon(true); //在线程运行之前调用
4 start();
5 }
6
7 public void run() {
8 while(true) {
9 try {
10 this.sleep(100);
11 } catch (InterruptedException e) {
12 // TODO Auto-generated catch block
13 e.printStackTrace();
14 }
15 System.out.println(this);
16 }
17 }
18 /** *//**
19 * @param args
20 */
21 public static void main(String[] args) {
22 // TODO Auto-generated method stub
23 for (int i=0; i<10; i++)
24 new SimpleDaemon();
25 }
26
27}
28
该程序没有运行结果,因为在main()中创建完所有线程以后,程序已经没有执行下去的理由,因为所有的非后台进程都已经结束,所以剩下的十个后台进程将没有机会运行他们的打印语句。这里需要注意的是,把线程设置为后台进程的操作一定要在线程执行前进行。由一个后台线程创建的所有线程将都默认是后台线程。
机房快关门了,今天就到此为止了,以上是我的学习笔记,希望能给大家提供一定的方便。
2008年3月24日21:46:56
posted on 2008-03-24 21:48
piggytommy 阅读(84)
评论(0) 编辑 收藏 所属分类:
Java Basic