码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2015-08-16 12:27:39      阅读:180      评论:0      收藏:0      [点我收藏+]

标签:java   并发   线程   

《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]:5
Thread[Thread-0,10,main]:4
Thread[Thread-0,10,main]:3
Thread[Thread-0,10,main]:2
Thread[Thread-0,10,main]:1
Thread[Thread-1,1,main]:4
Thread[Thread-1,1,main]:3
Thread[Thread-3,1,main]:5
Thread[Thread-3,1,main]:4
Thread[Thread-3,1,main]:3
Thread[Thread-3,1,main]:2
Thread[Thread-3,1,main]:1
Thread[Thread-2,1,main]:5
Thread[Thread-2,1,main]:4
Thread[Thread-2,1,main]:3
Thread[Thread-1,1,main]:2
Thread[Thread-4,1,main]:5
Thread[Thread-4,1,main]:4
Thread[Thread-4,1,main]:3
Thread[Thread-4,1,main]:2
Thread[Thread-4,1,main]:1
Thread[Thread-1,1,main]:1
Thread[Thread-2,1,main]:2
Thread[Thread-2,1,main]:1
Thread[Thread-5,1,main]:5
Thread[Thread-5,1,main]:4
Thread[Thread-5,1,main]:3
Thread[Thread-5,1,main]:2
Thread[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也已经退出,程序结束。

版权声明:本文为博主原创文章,未经博主允许不得转载。

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

标签:java   并发   线程   

原文地址:http://blog.csdn.net/lb_383691051/article/details/47700543

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!