Chapter2: Thread Creation and Management
2.1 What Is a Thread?
介绍了什么是线程,以及线程(thread, multithread)与进程(process, mutltitask)的区别。其中的一个重要区别是对共享数据的访问。进程可以共享操作系统级别的某些数据区域,如剪贴板;而线程是对程序自有的数据进行共享。进程之间对共享数据的存取方法是特殊的,因此自然能够得到程序员的注意;而线程之间对共享数据的存取与对线程自己的数据的存取方法是一样的,所以常常比较隐蔽,而被程序员忽略。其实,这也是多线程开发比较难的地方。所以,这一节最后说:"A thread, then, is a discrete task that operates on data shared with other threads.(线程就是一个在与其它线程共享的数据上进行操作的单独任务。)"
2.2 Creating a Thread
1. 有两种方法创建线程:使用Thread类或者Runnable接口
2. 示例程序竟然是编一个打字游戏,所以,这本书的门槛还是有点高的:(。当然可以从该书的站点直接下载代码。
3. Thread class
package java.lang;
public class Thread implements Runnable {
public Thread( );
public Thread(Runnable target);
public Thread(ThreadGroup group, Runnable target);
public Thread(String name);
public Thread(ThreadGroup group, String name);
public Thread(Runnable target, String name);
public Thread(ThreadGroup group, Runnable target, String name);
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize);
public void start( );
public void run( );
}
Thread name: 线程名,用以打印线程信息时用,缺省为Thread-N。
Runnable target:可运行的目标。一个可运行的对象就是一串可由线程执行的指令。缺省就是在run()方法中编写的内容。
Thread group:线程组。对于大多数应用来说没什么意义,缺省在同一组。
Stack size:线程堆栈大小,与平台相关。为了使程序更具可移植性,并不推荐使用。
2.3 The Lifecycle of a Thread
1. Creating a Thread:创建一个线程对象,并没有开始执行一个线程
2. Starting a Thread:在调用线程对象的start()方法前,一直处于等待状态。可以通过调用isAlive()方法来获取线程是否正在运行;
3. Terminating a Thread:线程在以下情况下结束
1)执行完run()中的语句;
2)抛出一个异常,包括其没有捕获的异常;
3)其所在的程序(或者容器)调用System.exit()。
注意:一个线程结束后,就不能再次启动,该线程就变成一个普通的对象,如果试图再次调用start()方法,将抛出java.lang.IllegalThreadStateException异常。如果想多次运行,要么创建新的对象;要么就是不要结束该线程。
4. Java并没有提供可靠的方法来暂停、挂起或者重启一个线程的方法,线程本身能通过sleep()函数来暂停执行。目前依然只能确保若干毫秒级别的精度。
5. Thread Cleanup:线程终止后,线程对象依然存在。可以通过join()方法以阻塞方式(blocked)来等待某个线程终止。
2.4 Two Approches to Stop a Thread
1. Setting a Flag:在线程的执行过程中判断其它线程是否将标志置位。其缺点是有时间延迟,尤其是当进入了某些被阻塞的函数如:sleep(), wait()等;
2. 调用线程的interrupt()方法,线程的执行过程中改为判断isInterrupted()的返回值。可以解决线程进入阻塞导致的延迟,但依然解决不了超长时间计算而导致的延迟。interrupt()有两个作用:1)终止sleep()和wait(),抛出InterruptedException;2)使isInterrupted()返回true。下面这段代码将演示interrupt()的作用:
public class TestInterrupted {
public static void main(String args[]) throws InterruptedException{
Foo foo = new Foo();
foo.start();
Thread.currentThread().sleep(100); //注释掉这句有什么影响呢?
System.out.println("before interrupt");
foo.interrupt();
System.out.println("after interrupt");
} }
class Foo extends Thread {
public void run() {
try{
while(!isInterrupted()) {
System.out.println("start calculating...");
double pi = 3.1415926;
for(int i=0; i<5; i++) {
for(long j=0; j<1000000; j++) {
pi *= pi;
}
System.out.println("i="+i);
}
System.out.println("before sleep");
sleep(5000); //注释掉这句及相关的try...catch语句,又怎样呢?
System.out.println("after sleep");
}
} catch(InterruptedException ex) {
ex.printStackTrace(System.out);
}
}
}
2.5 The Runnable Interface
为什么有了Thread类,还要有Runnable接口呢?最主要的原因是为了解决Java不能多重继承的问题。线程继承自Thread,还是仅仅实现Runnable接口,取决于这个线程的作用。不过,如果仅仅实现Runnable接口,则在线程里不能使用Thread类的方法,比如interrupt()和isInterrupted()。当然,这个还是要取决于实际情况。
2.6 Threads and Objects
主要讲解线程与对象的关系。线程根本上还是个对象,其可以存取任何对象包括其它线程对象,当然也能被其它对象存取,只要没有因为争夺公共资源而被锁定,线程对象中的任何属性和方法都有可能同时执行。并且Java中的锁只针对对象本身,并不包括对象下的属性;而对方法同步,则等同于对对象同步。更进一步的说明还可以参考《Practical Java》中的"实践46:面对instance函数,synchronized锁定的是对象(object)而非函数(methods)或代码(code)"。下面用两段代码来说明一下:
代码1:
public class TestLock {
public static void main(String args[])
throws InterruptedException{
Foo foo = new Foo();
foo.start();
Thread t = Thread.currentThread();
for(int i=0; i<10; i++) {
foo.setInt2("Main", i, i+20);
}
}
}
class Foo extends Thread{
protected int arrI[] = new int[10];
public void run() {
try {
for(int i=0; i<10; i++) {
setInt("Foo", i, i);
}
} catch(InterruptedException ex) {}
}
public synchronized void setInt(String from, int pos, int val)
throws InterruptedException{
arrI[pos] = val;
sleep((long)(Math.random()*5000));
System.out.println(from+":arrI["+pos+"]="+arrI[pos]);
}
public void setInt2(String from, int pos, int val)
throws InterruptedException {
synchronized(arrI){
arrI[pos] = val;
sleep((long)(Math.random()*5000));
System.out.println(from+":arrI["+pos+"]="+arrI[pos]);
}
}
}
结果:非线程安全,setInt()在对象上加锁,而setInt2()在属性arrI上加锁,不同的锁不能保证线程安全。可能的结果如下:
Foo:arrI[0]=0
Main:arrI[0]=0
Main:arrI[1]=21
Main:arrI[2]=22
Foo:arrI[1]=21
Main:arrI[3]=23
Main:arrI[4]=24
Main:arrI[5]=25
Foo:arrI[2]=2
Main:arrI[6]=26
Main:arrI[7]=27
Foo:arrI[3]=3
Foo:arrI[4]=4
Main:arrI[8]=28
Main:arrI[9]=29
Foo:arrI[5]=5
Foo:arrI[6]=6
Foo:arrI[7]=7
Foo:arrI[8]=8
Foo:arrI[9]=9
代码2:
public class TestLock1 {
public static void main(String args[])
throws InterruptedException{
Foo1 foo = new Foo1();
foo.start();
Thread t = Thread.currentThread();
for(int i=0; i<10; i++) {
foo.setInt2("Main", i, i+20);
}
}
}
class Foo1 extends Thread{
protected int arrI[] = new int[10];
public void run() {
try{
for(int i=0; i<10; i++) {
setInt("Foo", i, i);
}
}catch(InterruptedException ex){}
}
public synchronized void setInt(String from, int pos, int val)
throws InterruptedException{
arrI[pos] = val;
sleep((long)(Math.random()*5000));
System.out.println(from+":arrI["+pos+"]="+arrI[pos]);
}
public synchronized void setInt2(String from, int pos, int val)
throws InterruptedException{
arrI[pos] = val;
sleep((long)(Math.random()*5000));
System.out.println(from+":arrI["+pos+"]="+arrI[pos]);
}
}
结果:线程安全