读书笔记-java网络编程-3线程-线程调度

来源:互联网 发布:java写入excel换行 编辑:程序博客网 时间:2024/06/07 13:55

4. 线程调度

4.1线程调度

java中10是最高优先级,0是最低优先级,默认的优先级5。
这与Unix优先级相反。
Windows只有7个优先级。
尽量不要设置过高的优先级否则会导致低优先级饥饿。

public final void setPriority(int newPriority)

使用这条语句来指定设置优先级。

4.2 抢占

主要有两种线程调度机制:
抢占式(preemptive):调度器确定正常地轮到某cpu时间时,会暂停这个线程,将cpu控制权交给另外的线程。
协作式(coorperative):等待线程自己暂停。

所有java虚拟机都保证在不同优先级之间使用抢占式的线程调度,在同样优先级之间偶尔会暂停其中一个线程,让下一个线程得到一些cpu时间。

在jvm虚拟机的机制下很难发现的,此外需要注意的是如果客户端的程序可能是协作式的,因此也有可能陷入饥饿状态。

通常来说一个线程有10中方式可以暂停或者指示它准备暂停:
1. 可以对IO阻塞
2. 可以对同步对象阻塞
3. 可以放弃
4. 可以休眠
5. 可以连接另一个对象
6. 可以等待另一个对象
7. 可以结束
8. 可以被更高优先级线程抢占
9. 可以被挂起
10. 可以被停止
最后两种方法以及不使用了,因为它会使得对象处于不一致的状况。

4.3 线程切换方式种类

4.3.1阻塞

当网络程序中的线程自动放弃CPU控制权,最常见的方式是对IO阻塞。同样进入一个同步方法
对于IO阻塞一般不会造成严重后果,要不获得所继续进行,要么抛出IOException异常。
或者代码块的时候也会阻塞。如果没有这个同步对象的锁,则会陷入阻塞。
有可能造成死锁的严重后果。

4.3.2 放弃

线程可以通过调用Thread.yield()静态方法来做到。将通知虚拟机,如果有另一个线程准备运行,可以运行该线程。但是有些虚拟机会忽视这个提示。
在放弃之前应该确保它与它关联的Runnable对象处于一致状态,因此在理想情况下,在线程放弃时不做任何同步。

public void run(){    while(true){    //    Thread.yield();    }}

这使得其他有相同优先级的线程有机会运行

4.3.3 休眠

休眠与放弃类似,区别是不管有没有其他准备运行的线程,休眠的线程都会暂停。这样低优先级的线程会获得运行的机会。
缺点和线程一样,要避免在同步方法或块内让线程同步。

public static void sleep(long milliseconds) throws InterruptedExceptionpublic static void sleep(long milliseconds, int nanoseconds) throws InterruptedException

需要说明的是,这里的的秒速是不保证时间准确的,在实际编程的时候需要注意精度。

public void run(){    while(true){        if(!getPage("http://www.iboiblio.org")){            mailError("webmaster@ibiblio.org");        }        try{            Tread.sleep(300000);        }catch(InterruptedException ex){            break;        }    }}

有时候在时间到后没有办法立刻唤醒,因为JVM正在忙于其他的事情。同样也可能时间还没有到就中断了。通过

public void interrupt()

来完成。这就是线程和Thread对象之间的区别。线程在休眠,并不意外着其他醒着的线程不能处理这个线程相应的Thread对象。具体的说,另一个线程可以调用休眠Thread对象的interrupt()方法,会让休眠中的线程得到一个InterruptedExcetpion异常。并处理这个异常。

如果一个线程对IO操作阻塞,中断这个线程的效果很大程度上依赖于具体的平台。通常这将是一个什么都不做的操作。也就是说线程继续阻塞。如果你需要可中断的IO,应该认真考虑非阻塞的IO,而非流。

4.3.4 链接线程

java提供了join()方法,允许一个线程在运行之前等待其他线程结束。

public final void join() throws InterruptedExceptionpublic final void join(long milliseconds) throws InterruptedExceptionpublic final void join(long milliseconds, int nanoseconds) throws InterruptedException

这里把书上的例子 稍加改动变成了可以执行的版本如下:

package thread;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.security.DigestInputStream;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;public class ReturnDigest extends Thread {    private String filename;    private JoinDigestUserInterface callback;    public ReturnDigest(String filename,JoinDigestUserInterface callback) {        this.filename = filename;        this.callback = callback;    }    public void run() {        FileInputStream in;        try {            in = new FileInputStream(filename);            MessageDigest sha = MessageDigest.getInstance("SHA-256");            DigestInputStream din = new DigestInputStream(in, sha);            while (din.read()!=-1) ;            din.close();            callback.setDigest(sha.digest());        } catch (NoSuchAlgorithmException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }catch (FileNotFoundException e) {            // TODO Auto-generated catch block            e.printStackTrace();        } catch (IOException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }    }}
package thread;import javax.xml.bind.DatatypeConverter;import org.junit.Test;public class JoinDigestUserInterface {    private byte[] digest;    public void setDigest(byte[] digest) {        this.digest = digest;    }    @Test    public void test(){        String[] list = new String[3];        list[0] = "E:\\tomcat\\webapps\\network\\bin\\thread\\test1.txt";        list[1] = "E:\\tomcat\\webapps\\network\\bin\\thread\\test2.txt";        list[2] = "E:\\tomcat\\webapps\\network\\bin\\thread\\test3.txt";        ReturnDigest[] digestThreads = new ReturnDigest[list.length];        for(int i = 0 ; i < 3 ; i++){            digestThreads[i] = new ReturnDigest(list[i],this);            digestThreads[i].start();        }           for(int i = 0 ; i < 3 ; i++){            try{                digestThreads[i].join();                //                StringBuffer result = new StringBuffer(list[i]);                result.append(":");                result.append(DatatypeConverter.printHexBinary(digest));                System.out.println(result);            }catch(InterruptedException ex){                System.out.println("Thread Interrupt before completion");            }        }    }}

4.3.5 等待一个对象

线程可以等待一个它锁定的对象,在等待时,它会释放这个对象的锁并暂停,知道它得到其他线程的通知。
另一个线程以某种方式修改这个对象,通知等待对象的线程然后继续执行。
与连接不同的是,这里等待的是资源达到某种状态而不是等待线程结束。

public final void wait() throws InterruptedExceptionpublic final void wait(long milliseconds) throws InterruptedExceptionpublic final void wait(long milliseconds, int nanoseconds) throws InterruptedException

需要注意的是以上的方法是属于object类的。因此任何对象都可以调用上述的方法
调用时,它所属的线程会释放所等待对象的锁,但不会释放它所拥有的其他资源的锁。并进入休眠,线程也会休眠。直到发生下列3中情况之一:
- 时间到期
- 线程被中断
- 对象得到通知

超时时间(timeout)与sleep()和join()方法中的超时时间相同,即线程经过指定的一段时间后会唤醒。当时间到期,线程会从wait后开始执行,不过如果线程不能立即获得所等待对象的锁,它可能会继续阻塞
中断(Interruption)其他线程调用这个线程的interrupt方法。导致一个InterruptedException异常,并在捕获这个异常的catch内继续运行。不过在抛出异常钱线程要重新获得所等待对象的锁,因此调用interrupt方法后该线程可能仍然要阻塞一段时间。
通知(notification),在其他线程在这个线程所等待的对象上调用notify或者notifyall方法就会发生通知。这两个方法都在object中

public final void notify()public final void notifyall()

再通知一个对象之前,线程必须首先使用同步方法或块获得了对象的锁。notify()基本上随机地从等待这个对象的线程列表上选择一个线程,并将其唤醒。notifyall将唤醒所有线程
一旦等待线程得到通知,它就试图重新获得所等待对象的锁,如果成功就继续从wait后执行,如果失败就阻塞这个对象,直到获得锁。

这里少段程序

当多个线程希望等待同一个对象时,等待和通知会更为常见。
如果有多个线程在等待这个对象,首选notifyall,因为没有办法选择要通知哪个线程。
一般要将wait调用放在检测当前对象状态的循环中,不要假定因为线程得到了通知,对象现在就处于正确的状态。

这里少段程序

4.3.5 结束

当run方法返回时,线程将撤销,其他线程可以接管CPU。
在网络应用程序中,包装一个阻塞操作的线程往往会这样做,例如从服务器下载一个文件,这样应用程序的其他部分就不会被阻塞了。

1 0