JAVA编程思想学习总结:第21章第1-2节基本的线程机制

来源:互联网 发布:stl源码剖析 编辑:程序博客网 时间:2024/05/28 19:23

/*这一章的内容很多,而且很难,我不得不分成多个小节来慢慢理解*/

(1)多线程实现

接下来的这段代码中将会用两种方式实现多线程实例,第一种通过实现Runnable接口,第二种使用Thread构造器。
其中,静态方法Thread.yield()调用是对线程调度器(JAVA线程机制的一部分,可以将CPU从一个纯种转移给另一个线程)的一种建议,它在声明:“我已经执行完生命周期中最重要的部分了,此刻正是切换给其他任务执行一段时间的大好时机。另一种影响任务行为的简单方法是sleep()。下例程序中可将yield方法换成sleep方法看运行结果。
Thread构造器需要一个Runnable对象。调用Thread对象的start()方法为该线程执行必需的初始化操作,然后调用Runnable的run()方法。
package concurrent;/*  *P654-655 */class LiftOff implements Runnable{protected int countDown=10;private static int taskCount=0;private final int id=taskCount++;public LiftOff(){};public LiftOff(int countDown){this.countDown=countDown;}public String status(){return "#" +id+"("+(countDown>0?countDown:"Liftoff!")+").";}public void run(){while(countDown-->0){System.out.print(status());Thread.yield();}}}public class MainThread {public static void main(String[] args){int i=0;for(i=0;i<2;i++){//使用Runnable接口LiftOff launch=new LiftOff();launch.run();}System.out.println("Runnable start");for(i=0;i<2;i++){//使用Thread构造方法new Thread(new LiftOff()).start();}System.out.println("Thread start");}}


输出结果:
#0(9).#0(8).#0(7).#0(6).#0(5).#0(4).#0(3).#0(2).#0(1).#0(Liftoff!).#1(9).#1(8).#1(7).#1(6).#1(5).#1(4).#1(3).#1(2).#1(1).#1(Liftoff!).Runnable start
Thread start
#2(9).#2(8).#2(7).#2(6).#2(5).#2(4).#2(3).#2(2).#2(1).#2(Liftoff!).#3(9).#3(8).#3(7).#3(6).#3(5).#3(4).#3(3).#3(2).#3(1).#3(Liftoff!).
Executor在客户端和任务执行之间提供了一个间接层;与客户端直接执行任务不同,这个中介对象将执行任务。Executor允许你管理异步任务的执行,而无须显式地管理线和的生命周期。
CachedThreadPool将为每个任务都创建一个线程,ExecutorService对象是使用静态的Executor方法创建的,这个方法可以确定其Executor类型。
FixedThreadPool可以一次性预告执行代价高昂的线程分配,这样做可以节省时间,不用为每个任务都固定付出创建线程的开销,但是必须要预先设定创建线程的数量。
CachedThreadPool在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程,因此它是合理的Executor的首选。
SingleThreadExecutor就像是线程数量为1的FixedThreadPool。如果向SingleThreadExecutor提交多个任务,那么这些任务将排队,每个任务都会在下一个任务开始之前运行结束,所有的任务将使用相同的线程。SingleThreadExecutor会序列化所有提交给它的任务,并维护它自己的悬挂任务队列。
package concurrent;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;//liftOff()对象上一段代码中已经声明,放在同一个包下,无需再次申明public class CachedThreadPool {public static void main(String[] args){//ExecutorService exec=Executors.newCachedThreadPool();//该行语句与接下来的线程语句分别执行,看运行结果//ExecutorService exec =Executors.newFixedThreadPool(5);ExecutorService exec=Executors.newSingleThreadExecutor();for(int i=0;i<5;i++){exec.execute(new LiftOff());}exec.shutdown();//shutdown()方法可以防止新任务被提交给这个Executor}}

输出结果:运行Executors.newCachedThreadPool方法。
#0(9).#4(9).#2(9).#1(9).#3(9).#4(8).#3(8).#0(8).#3(7).#1(8).#4(7)
.#0(7).#4(6).#0(6).#4(5).#0(5).#4(4).#0(4).#4(3).#0(3).#4(2).#0(2).#4(1).#0(1).
#4(Liftoff!).#0(Liftoff!).#2(8).#3(6).#1(7).#3(5).#1(6).#2(7).#2(6).#2(5).#2(4).#2(3).#2(2).#2(1).
#2(Liftoff!).#1(5).#1(4).#1(3).#1(2).#1(1).#1(Liftoff!).#3(4).#3(3).#3(2).#3(1).#3(Liftoff!).
运行Executors.newFixedThreadPool方法。
#1(9).#0(9).#3(9).#1(8).#3(8).#1(7).#3(7).#1(6).#3(6).#1(5).#3(5).
#2(9).#3(4).#1(4).#3(3).#1(3).#3(2).#1(2).#3(1).#1(1).#2(8).#0(8).
#4(9).#1(Liftoff!).#2(7).#0(7).#4(8).#3(Liftoff!).#2(6).#0(6).#4(7).
#2(5).#0(5).#4(6).#2(4).#0(4).#4(5).#2(3).#0(3).#4(4).#2(2).#0(2).#4(3).
#2(1).#0(1).#4(2).#2(Liftoff!).#0(Liftoff!).#4(1).#4(Liftoff!).
运行Executors.newSingleThreadExecutor方法
#0(9).#0(8).#0(7).#0(6).#0(5).#0(4).#0(3).#0(2).#0(1).#0(Liftoff!).
#1(9).#1(8).#1(7).#1(6).#1(5).#1(4).#1(3).#1(2).#1(1).#1(Liftoff!).
#2(9).#2(8).#2(7).#2(6).#2(5).#2(4).#2(3).#2(2).#2(1).#2(Liftoff!).
#3(9).#3(8).#3(7).#3(6).#3(5).#3(4).#3(3).#3(2).#3(1).#3(Liftoff!).
#4(9).#4(8).#4(7).#4(6).#4(5).#4(4).#4(3).#4(2).#4(1).#4(Liftoff!).

(2)从任务中返回值

如果希望任务在完成时能够返回一个值,那么可以实现Callable接口而不是Runnable接口。Callable是一个具有类型参数的泛型,它的类型参数表示的是从方法call()返回的值,并且必须使用ExecutorService,submit()方法调用它。
submit()方法会产生Future对象,它用Callable返回结果的特定类型进行了参数化。可以使用isDone()方法来查询Future是否已经完成。当任务完成时,它具有一个结果,可以调用get()方法来获取该结果。也可以不用idDone()进行检查就直接调用get(),这种情况下,get()将阻塞,直到结果准备就绪。
class TaskWithResult implements Callable<String>{private int id;public TaskWithResult(int id){this.id=id;}public String call(){return "result of TaskWithResult "+id;}}public class CallableDemo {public static void main(String[] args){ExecutorService exec=Executors.newCachedThreadPool();ArrayList<Future<String>> results=new ArrayList<Future<String>>();for(int i=0;i<10;i++){results.add(exec.submit(new TaskWithResult(i)));}for(Future<String> fs:results){try{System.out.println(fs.get());}catch(InterruptedException e){System.out.println(e);return;}catch(ExecutionException e){System.out.print(e);}finally{exec.shutdown();}}}}
输出结果:
result of TaskWithResult 0
result of TaskWithResult 1
result of TaskWithResult 2
result of TaskWithResult 3
result of TaskWithResult 4
result of TaskWithResult 5
result of TaskWithResult 6
result of TaskWithResult 7
result of TaskWithResult 8
result of TaskWithResult 9

(3)优先级

线程的优先级将该线程的重要性传递给了调度器。尽管CPU处理现有线程集的顺序是不确定的,但是调度器将倾向于让优先权最高的线程先执行。
注意:虽然下面的程序在书中表明是可以用来体现线程优先级的,但是我在win 7,Eclipse,多核条件下,并不能得到有规律的结果。书中给出的结果示例表明高优先级的线程为一直运行到跳出循环为止,期间其它线程无法得到CPU资源,但是我的运行结果并不如此。反而在如书上所说,注释掉for循环的内容后,得到和书中一样的结果。
public class SimplePriorities implements Runnable{private int countDown=5;private volatile double d;private int priority;public SimplePriorities(int priority){this.priority=priority;}public String toString(){return Thread.currentThread()+": "+countDown;}public void run(){Thread.currentThread().setPriority(priority);while(true){/*for(int i=1;i<100000;i++){//注释掉该for循环,看运行结果如何this.d +=(Math.PI+Math.E)/(double)i;if(i%1000==0)Thread.yield();}*/System.out.println(this);if(--countDown ==0)return;}}public static void main(String[] args){ExecutorService exec =Executors.newCachedThreadPool();for(int i=0;i<5;i++){exec.execute(new SimplePriorities(Thread.MIN_PRIORITY));}exec.execute(new SimplePriorities(Thread.MAX_PRIORITY));exec.shutdown();}}
虽然向控制台打印也是开销较大的操作,但在那种情况下看不出优先级的效果,因为向控制台打印不能被中断(否则的话,在多线程情况下控制台显示就混乱了),而数学运算是可以中断的。这里去处时间足够长,因此纯种高度机制才来得及介入,交换任务并关注优先级,使得最高优先级的线程被优先选择。
尽管JDK有10个优先级,但它与多数操作系统都不能映射的很好。比如,windosw有7个优先级且不固定,所以这种映射关系也不确定。唯一可移植的方法是当调整优先级的时候,只使用MAX_PRIORITY,NORM_PRIORITY和MIN_PRIORITY。

(4)后台线程

所谓后台线程,是指在程序运行的时候在后台提供一种通用的服务线程,并且这种纯种并不属于程序中不可或缺的部分。因此,当所有的非后台线程结束时,程序也就终止了,同时会杀毒进程中的所有后台线程。
必须在线程启动之前,调用setDaemon()方法,才能把它设置为后台线程。
可以调用isDaemon()方法来确定线程是否是一个后台线程,如果是一个后台线程,那么它创建的任何纯种将被自动设置成后台线程。
后台进程在不执行finally子句的情况下就会终止其run()方法。因为当最后一个非后台线程终止时,后台线程会突然终止。JVM会在main()退出后不给任何提示的关闭所有后台进程,所以后台线程并不是一种很好的思想。

(5)Thread继承

除了实现Runnable,还可以直接从Thread继承实现。
以下代码会尝试在构造器中启动线程,但是,在构造器中启动线程可能会变得很有问题,因为另一个任务可能会在构造器结束之前开始执行,这意味着该任务能够访问处于不稳定状态的对象。
package concurrent;import java.util.Currency.*;import java.util.concurrent.TimeUnit;/* * P666-668 * 书中第669页有说明各个方法的区别及意义 *///使用一个继承自Thread的内部类class InnerThread1{private int countDown=5;private Inner inner;private class Inner extends Thread{Inner(String name){super(name);start();}public void run(){try{while(true){System.out.println(this);if(--countDown==0) return;sleep(10);}}catch(InterruptedException e){System.out.println("interrupted");}}public String toString(){return getName() +": "+countDown;}}public InnerThread1(String name){inner =new Inner(name);}}//直接包含一个Thread类class InnerThread2{private int countDown=5;private Thread t;public InnerThread2(String name){t=new Thread(name){public void run(){try{while(true){System.out.println(this);if(--countDown==0)return;sleep(10);}}catch(InterruptedException e){System.out.println("Slee() interrupted");}}public String toString(){return getName() +": "+countDown;}};t.start();}}//内部类实现一个Runnable的接口class InnerRunnable1{private int countDown =5;private Inner inner;private class Inner implements Runnable{Thread t;Inner(String name){t=new Thread(this,name);t.start();}public void run(){try {while (true){System.out.println(this);if(--countDown==0) return ;TimeUnit.MILLISECONDS.sleep(10);}}catch(InterruptedException e){System.out.println("Sleep() interrupted");}}public String toString(){return t.getName()+": "+countDown;}}public InnerRunnable1(String name){inner =new Inner(name);}}//使用匿名的Runnable接口class InnerRunnable2{private int  countDown=5;private Thread t;public InnerRunnable2(String name){t=new Thread (new Runnable(){public void run(){try{while(true){System.out.println(this);if(--countDown == 0) return;TimeUnit.MICROSECONDS.sleep(10);}}catch(InterruptedException e){System.out.println("sleep() interrupted");}}public String toString(){return Thread.currentThread().getName()+": "+countDown;}},name);t.start();}}//让代码像任务一样运行,适合当线程只是执行辅助操作.class ThreadMethod{private int countDown =5;private Thread t;private String name;public ThreadMethod(String name) {this.name=name;}public void runTask(){if(t==null){t=new Thread(name){public void run(){try{while(true){System.out.println(this);if(--countDown ==0)return ;sleep(10);}}catch(InterruptedException e){System.out.println("sleep() interrupted");}}public String toString(){return getName() +": "+countDown;}};t.start();}}}public class ThreadVariations {public static void main(String[] args){new InnerThread1("InnerThread1");new InnerThread2("InnerThread2");new InnerRunnable1("InnerRunnable1");new InnerRunnable2("InnerRunnable2");new ThreadMethod("ThreadMethod").runTask();}}

(6)加入线程

一个纯种可以在其他线程之上调用join()方法,其效果是等待一段时间直到第二个线程结束才继续执行。如果某个线程在另一个线程t上调用t.join(),此线程将被挂起,直到目标线程t结束才恢复(即t。isAlive()返回为假)。
也可以在调用join()时带上一个超时参数(单位可以是毫秒,或者毫秒和纳秒),这样如果目标线程在这段时间到期时还没有结束的话,join()方法总能返回。
对join()方法的调用可以被中断,做法是在调用线程上调用interrupt()方法,这时需要用到try-catch子句。
package concurrent;/* * P670 */class Sleeper extends Thread{private int duration;public Sleeper(String name,int sleepTime){super(name);duration = sleepTime;start();}public void run(){try{sleep(duration);}catch(InterruptedException e){System.out.println(getName() +" was interrupted. "+"isInterrupted(): " + isInterrupted());return ;}System.out.println(getName()+" has awakened");}}class Joiner extends Thread{private Sleeper sleeper;public Joiner(String name ,Sleeper sleeper){super(name);this.sleeper=sleeper;start();}public void run(){try{sleeper.join();}catch(InterruptedException e){System.out.println("Interrupted");}System.out.println(getName() + " join completed");}}public class Joining {public static void main(String[] args){Sleeper sleepy=new Sleeper("Sleepy",1500);Sleeper grumpy=new Sleeper("Grumpy",1500);Joiner doper=new Joiner("Dopey",sleepy),doc =new Joiner("Doc",grumpy);grumpy.interrupt();}}
运行结果:
Grumpy was interrupted. isInterrupted(): false
Doc join completed
Sleepy has awakened
Dopey join completed

(7)捕获异常

由于线程的本质特性,以前并不能捕获从线程中逃逸的异常。一旦异常逃出任务的run()方法,就会向外传播到控制台。
为了解决这个问题,java SE5中的新接口Thread.UncaughtExceptionHandle,它允许在每个Thread对象上都附着一个异常处理器。
package concurrent;/* * P673 */import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.ThreadFactory;class ExceptionThread2 implements Runnable{public void run(){Thread t=Thread.currentThread();System.out.println("run() by "+t);System.out.println("eh = " +t.getUncaughtExceptionHandler());throw new RuntimeException();}}class MyUncaughtExceptionHandler implementsThread.UncaughtExceptionHandler{public void uncaughtException(Thread t,Throwable e){System.out.println("caught " +e);}}class HandlerThreadFactory implements ThreadFactory{public Thread newThread(Runnable r){System.out.println(this + " creating new Thread");Thread t=new Thread(r);System.out.println("created "+t);t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());System.out.println("eh = " +t.getUncaughtExceptionHandler());return t;}}public class CaptureUncaughtException {public static void main(String[] args){ExecutorService exec = Executors.newCachedThreadPool(new HandlerThreadFactory());//ExecutorService exec = Executors.newCachedThreadPool();//exec.execute();//System.out.println("let me look");exec.execute(new ExceptionThread2());//Executors.n}}
运行结果:
concurrent.HandlerThreadFactory@4e25154f creating new Thread
created Thread[Thread-0,5,main]
eh = concurrent.MyUncaughtExceptionHandler@70dea4e
run() by Thread[Thread-0,5,main]
eh = concurrent.MyUncaughtExceptionHandler@70dea4e
concurrent.HandlerThreadFactory@4e25154f creating new Thread
created Thread[Thread-1,5,main]
eh = concurrent.MyUncaughtExceptionHandler@4a8f0963
caught java.lang.RuntimeException
很遗憾的是,我并不能理解为什么结果会是这个样子。书中的结果并不是这样,我倒是能勉强理解书中的运行结果,但对于这一个,我实在理解不了。它看起来建立了两个线程,但实际上我只建立了一个。
0 0
原创粉丝点击