学习笔记(九)并发(二)

来源:互联网 发布:hid门禁软件 编辑:程序博客网 时间:2024/05/21 10:58

《Java编程思想》整理的一些学习笔记,有不对的地方,欢迎指出。
1.控制线程行为的方法——让步:如果知道run()方法已经完成了所需的工作,可以给线程调度机制一个暗示:你的工作已经做的差不多了,可以让别的线程使用CPU了,可以通过调用yield()方法来作出(不过这只是个暗示,没有任何机制保证它将会被采纳。)使用yield()以后,程序的输出会平衡很多,但是如果输出的字符串要再长一点的话,它还会是打破这种平衡,因为调用机制是抢占式的,如果输出字符串过长,占用时间较长,调用机制会在有机会调用yield()方法之前切到下一线程。

public class Demo extends Thread{    private int countDown = 5;    private static int threadCount = 0;    public Demo(){        super(""+ ++threadCount);              // 调用Tread构造器,给线程对象指定一个名字        start();    }    public String toString(){        return "#"+getName()+":"+ countDown;  //  使用getName()方法获取线程的名字    }    public void run(){        while(true){            System.out.println(this);            if(--countDown == 0)                return;            yield();        }    }    public static void main(String[] args){        for(int i = 0; i < 5; i++){            new Demo();        }    }}

2.另一只能控制线程行为的方法是调用sleep(),这将使线程停止执行一段时间,该时间由给定的毫秒数决定。如果把上例中的yield()的调用换成调用sleep():

public class Demo extends Thread{    private int countDown = 5;    private static int threadCount = 0;    public Demo(){        super(""+ ++threadCount);              // 调用Tread构造器,给线程对象指定一个名字        start();    }    public String toString(){        return "#"+getName()+":"+ countDown;  //  使用getName()方法获取线程的名字    }    public void run(){        while(true){            System.out.println(this);            if(--countDown == 0)                return;            try{                sleep(100);            }catch(InterruptedException e){                throw new RuntimeException(e);            }        }    }    public static void main(String[] args) throws InterruptedException{        for(int i = 0; i < 5; i++){            new Demo().join();        }    }}

其输出结果为:

#1:5#1:4#1:3#1:2#1:1#2:5#2:4#2:3#2:2#2:1#3:5#3:4#3:3#3:2#3:1#4:5#4:4#4:3#4:2#4:1#5:5#5:4#5:3#5:2#5:1

在调用sleep()方法的时候,必须把它放在try块中,这是因为sleep()方法在休眠时间到期之前有可能被中断。如果某人持有对此线程的引用,并且在此线程上调用了interrupt()方法,就会发生这种情况。(如果对线程调用了wait()或join()方法,interrupt()方法也会对线程有影响,所以对这些方法的调用也要放在try块中)通常如果想使用interrupt()方法来中断一个挂起的线程,那么挂起的时候最好使用wait()而不是sleep(),这样就不太可能在catch子句里结束了。
上例中调用了join()方法,所以main()在继续执行之前会等待此线程结束在执行下一个,如果不调用Join()方法,则输出还是任意顺序,这说明sleep()也不是控制线程执行顺序的方法,它仅仅使线程停止一段时间。(如果必须要控制线程的执行顺序,最好是根本不用线程,而是自己编写以特定顺序彼此控制的协作子程序)。
3.线程的“优先权”能告诉调度程序该线程的重要性如何。尽管CPU处理现有的线程集的顺序是不确定的,但是如果有许多线程被阻塞并在等待运行,那么调度程序将倾向于让优先权最高的线程先执行。然而,这并不意味着优先权较低的线程将得不到执行(即优先权不会导致死锁)。优先级较低的线程仅仅是执行的频率较低。线程的优先权是通过使用setPriority()方法进行调整的。

public class Demo extends Thread{    private int countDown = 5;//  private static int threadCount = 0;    private volatile double d = 0;    public Demo(int priority){//      super(""+ ++threadCount);              // 调用Tread构造器,给线程对象指定一个名字        setPriority(priority);        start();    }    public String toString(){//      return "#"+getName()+":"+ countDown;  //  使用getName()方法获取线程的名字        return super.toString()+":"+ countDown;     }    public void run(){        while(true){//          System.out.println(this);//          if(--countDown == 0)//              return;//          try{//              sleep(100);//          }catch(InterruptedException e){//              throw new RuntimeException(e);//          }            for(int i = 1; i < 100000; i++){                d = d+(Math.PI+Math.E)/(double)i;                System.out.println(this);                if(--countDown == 0)                    return;            }        }    }    public static void main(String[] args) throws InterruptedException{        new Demo(Thread.MAX_PRIORITY);        for(int i = 0; i < 5; i++){            new Demo(Thread.MIN_PRIORITY);        }    }}

运行结果:

Thread[Thread-0,10,main]:5Thread[Thread-0,10,main]:4Thread[Thread-0,10,main]:3Thread[Thread-0,10,main]:2Thread[Thread-0,10,main]:1Thread[Thread-1,1,main]:4Thread[Thread-1,1,main]:3Thread[Thread-3,1,main]:5Thread[Thread-3,1,main]:4Thread[Thread-3,1,main]:3Thread[Thread-3,1,main]:2Thread[Thread-3,1,main]:1Thread[Thread-2,1,main]:5Thread[Thread-2,1,main]:4Thread[Thread-2,1,main]:3Thread[Thread-1,1,main]:2Thread[Thread-4,1,main]:5Thread[Thread-4,1,main]:4Thread[Thread-4,1,main]:3Thread[Thread-4,1,main]:2Thread[Thread-4,1,main]:1Thread[Thread-1,1,main]:1Thread[Thread-2,1,main]:2Thread[Thread-2,1,main]:1Thread[Thread-5,1,main]:5Thread[Thread-5,1,main]:4Thread[Thread-5,1,main]:3Thread[Thread-5,1,main]:2Thread[Thread-5,1,main]:1

在本例中,toString()方法被覆盖,以便使用Thread.toString()方法来打印线程的名称(可以通过构造器自己设置名称,这里是自动生成的名称)以及线程的优先级、线程所属的“线程组”。线程0的优先级最高,其余线程的优先权被设为最低。
当然,对于已存在的线程,还可以用getPriority()方法得到其优先权,还可以在任何时候使用setPriority()方法更改其优先权。尽管JDK有10个优先级别,但是与操作系统映射不确定,所以一般只使用MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY三种级别。
4. 后台进程:是指在程序运行的时候在后台提供的一种通用服务的线程,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非后台线程结束时,程序也就终止了。即只要有非后台线程还在运行,程序就不会终止。比如,执行main()的就是一个非后台线程。

public class Demo extends Thread{    public Demo(){        setDaemon(true);        start();    }    public void run(){        while(true){            try{                sleep(100);            }catch(InterruptedException e){                throw new RuntimeException(e);            }            System.out.println(this);        }    }    public static void main(String[] args) {        for(int i = 0; i < 5; i++){            new Demo();        }    }}

必须在线程启动之前调用setDaemon()方法,才能把它设置为后台线程。在run()里面,线程被设定为休眠一段时间。一旦所有的线程都启动了,程序马上会在所有的线程能打印信息之前立刻终止,因为没有非后台线程(除了main())使得程序保持运行。因此,程序未打印任何信息就终止了。
可以通过调用isDaemon()方法来确定线程是否是一个后台线程,如果是一个后台线程,那么它创建任何的线程将被自动设置成后台线程。
5.如果你的类已经继承了其它的类,就不可能同时继承Thread,此时可以使用实现Runnable接口的方法来达到上述目的。要实现Runnable接口,只需实现run()方法,而且Thread也是从Runnable接口实现而来的。Runnable类中只有一个run()方法,如果想对你创建的这个Thread对象做点别的事情(比如在toString()里调用getName()),那么就必须通过调用Thread.currentThread( )方法明确得到对此线程的引用。例子:

public class Demo implements Runnable{    private int countDown = 5;    public String toString(){        return "#"+Thread.currentThread().getName()+":"+countDown;    }    public void run(){        while(true){            System.out.println(this);            if(--countDown == 0)                return;        }    }    public static void main(String[] args) {        for(int i = 1; i <= 5; i++){            new Thread(new Demo(),""+i).start();        }    }}

new Thread(new Demo(),”“+i):调用Thread另一个构造器,以Runnable的对象作为参数。
6.当使用了Runnable,通常的意思就是,要用run()方法中所实现的这段代码创建一个进程,而不是创建一个对象表示该进程。一个是把线程作为一个对象来表示,另一个是作为一个完全不同的一个实体(即进程)来表示。这点是有争议的。如果仅仅是想开启一个进程以驱动程序的某个部分,就没有理由把整个类写成Runnable类型的。因此,使用内部类把和线程有关的代码隐藏在类的内部,似乎更合理。有五中内部类的形式:
1)使用普通内部类继承Thread类
2)使用匿名内部类构造Thread类,重写run()方法
3)使用普通内部类实现Runnable类
4)使用匿名内部类构造Thread类,参数1构造一个Runnable对象,参数2指明线程名称
5)使用局部内部类,即在方法内部构造Thread类,重写run()方法
(推荐使用第五种,代码会在后续的文章中给出)
7.建立有响应的用户界面,下面例子中比较了不使用线程和使用线程的区别。

class UnresponsiveUI{    private volatile double d = 1;    public UnresponsiveUI() throws Exception{        while(d > 0)            d = d + (Math.PI+Math.E) / d;        System.in.read();    }}public class ResponsiveUI extends Thread {    private static volatile double d = 1;    public ResponsiveUI(){        setDaemon(true);        start();    }    public void run(){        while(true){            d = d + (Math.PI+Math.E) / d;        }    }    public static void main(String[] args) throws Exception{        new UnresponsiveUI();         new ResponsiveUI();        Thread.sleep(300);        System.in.read();        System.out.println(d);    }}

第一个UnresponsiveUI类中,while循环忙于计算,根本达不到System.in.read()这句。第二个ResponsiveUI 类中,由于把线程设为后台线程的缘故,一旦main()方法中输入数据,点击Enter后,main()方法执行结束,后台线程即使没结束,JVM也已经退出,程序结束。

1 0