Java 控制线程的方法

Java 的线程支持提供了一些便捷的工具方法,通过这些便捷的工具方法可以很好地控制线程的执行。

join 线程

Thread 提供了让一个线程等待另一个线程完成的方法—— join() 方法。当在某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到被 join() 方法加入的 join 线程执行完为止。

join() 方法通常由使用线程的程序调用,以将大问题划分成许多小问题,每个小问题分配一个线程。当所有的小问题都得到处理后,再调用主线程来进一步操作。

public class JoinThread extends Thread{
  // 提供一个有参数的构造器,用于设置该线程的名字
  public JoinThread(String name) {
    super(name);
  }
  //重写run()方法,定义线程执行体
  public void run() {
    for (int i=0 ; i < 100; i++) {
      System.out.println(this.getName() + " " + i);
    }
  }
  public static void main(String[] args) throws Exception {
    //启动子线程
    new JoinThread("新线程").start();
    for(int i=0 ; i < 100; i++) {
      if(i==20) {
        JoinThread jt = new JoinThread("被Join的线程");
        jt.start();
        // main线程调用了 jt 线程的 join() 方法
        // main线程必须等jt执行结束后才会向下执行
        jt.join();
      }
      System.out.println(Thread.currentThread().getName() + " " + i);
    }
  }
}

上面程序中一共有3个线程,主方法开始时就启动了名为“新线程”的子线程,该子线程将会和 main 线程并发执行。当主线程的循环变量 i 等于20时,启动了名为“被 Join 的线程”的线程,该线程不会和 main 线程并发执行 , main 线程必须等该线程执行结束后才可以向下执行。在名为“被 Join 的线程”的线程执行时,实际上只有2个子线程并发执行,而主线程处于等待状态。运行上面程序,会看到如下图所示的运行效果。

主线程执行到 i == 20时,程序启动并 join 了名为“被 Join 的线程”的线程,所以主线程将一直处于阻塞状态,直到名为“被 Join 的线程”的线程执行完成。

join() 方法有如下三种重载形式:

  • join():等待被 join 的线程执行完成。
  • join(long  millis):等待被 join 的线程的时间最长为 millis 毫秒。如果在 millis 毫秒内被 join 的线程还没有执行结束,则不再等待。
  • join(long millis, int nanos):等待被 join 的线程的时间最长为 millis 毫秒加 nanos 毫微秒。

提示:通常很少使用第三种形式,原因有两个:程序对时间的精度无须精确到毫微秒;计算机硬件、操作系统本身也无法精确到毫微秒。

后台线程

有一种线程,它是在后台运行的,它的任务是为其他的线程提供服务,这种线程被称为“后台线程(Daemon Thread)”,又称为“守护线程”或“精灵线程”。 JVM 的垃圾回收线程就是典型的后台线程。

后台线程有个特征:如果所有的前台线程都死亡,后台线程会自动死亡。

调用 Thread 对象的 setDaemon(true) 方法可将指定线程设置成后台线程。下面程序将执行线程设置成后台线程,可以看到当所有的前台线程死亡时,后台线程随之死亡。当整个虚拟机中只剩下后台线程时,程序就没有继续运行的必要了,所以虚拟机也就退出了。

public class DaemonThread extends Thread {
  // 定义后台线程的线程执行体与普通线程没有任何区别
  public void run() {
    for (int i=0 ; i < 1000; i++) {
      System.out.println(this.getName() + " " + i);
    }
  }

  public static void main(String[] args) {
    DaemonThread t = new DaemonThread();
    // 将此线程设置为后台线程
    t.setDaemon(true);
    // 启动后台线程
    t.start();
    for (int i=0; i < 100; i++) {
      System.out.println(Thread.currentThread().getName() + " " + i);
    }
    // ---------程序执行到此处,前台线程(main线程)结束----------
    // 后台线程也应该随之结束
  }
}

上面程序中的粗体字代码先将t线程设置成后台线程,然后启动该线程,本来该线程应该执行到 i 等于999时才会结束,但运行程序时不难发现该后台线程无法运行到999,因为当主线程也就是程序中唯一的前台线程运行结束后,JVM 会主动退出,因而后台线程也就被结束了。

Thread 类还提供了一个 isDaemon() 方法,用于判断指定线程是否为后台线程。

从上面程序可以看出,主线程默认是前台线程,t线程默认也是前台线程。并不是所有的线程默认都是前台线程,有些线程默认就是后台线程——前台线程创建的子线程默认是前台线程,后台线程创建的子线程默认是后台线程。

注意:前台线程死亡后, JVM 会通知后台线程死亡,但从它接收指令到做出响应,需要一定时间。而且要将某个线程设置为后台线程,必须在该线程启动之前设置,也就是说,setDaemon(true) 必须在 start() 方法之前调用,否则会引发 IllegalThreadStateException异常。

线程睡眠:sleep

如果需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过调用 Thread 类的静态 sleep() 方法来实现。sleep() 方法有两种重载形式。

  • static void sleep(long millis):让当前正在执行的线程暂停millis毫秒,并进入阻塞状态,该方法受到系统计时器和线程调度器的精度与准确度的影响。
  • static void sleep(long millis, int nanos):让当前正在执行的线程暂停 millis 毫秒加 nanos 毫微秒,并进入阻塞状态,该方法受到系统计时器和线程调度器的精度与准确度的影响。

与前面类似的是,程序很少调用第二种形式的 sleep() 方法。

当当前线程调用 sleep() 方法进入阻塞状态后,在其睡眠时间段内,该线程不会获得执行的机会,即使系统中没有其他可执行的线程,处于 sleep() 中的线程也不会执行,因此 sleep() 方法常用来暂停程序的执行。

下面程序调用 sleep() 方法来暂停主线程的执行,因为该程序只有一个主线程,当主线程进入睡眠后,系统没有可执行的线程,所以可以看到程序在 sleep() 方法处暂停。

public class SleepTest {
  public static void main(String[] args) throws Exception {
    for(int i=0;i<10;i++) {
      System.out.println("当前时间:"+new Date());
      // 调用sleep() 方法让当前线程暂停1s
      Thread.sleep(1000);
    }
  }
}

上面程序中的粗体字代码将当前执行的线程暂停 1 秒,运行上面程序,看到程序依次输出10条字符串,输出2条字符串之间的时间间隔为1秒。

线程让步: yield

yield() 方法是一个和 sleep() 方法有点相似的方法,它也是 Thread 类提供的一个静态方法,它也可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将该线程转入就绪状态。 yield() 只是让当前线程暂停一下,让系统的线程调度器重新调度一次,完全可能的情况是:当某个线程调用了 yield() 方法暂停之后,线程调度器又将其调度出来重新执行。

实际上,当某个线程调用了 yield() 方法暂停之后,只有优先级与当前线程相同,或者优先级比当前线程更高的处于就绪状态的线程才会获得执行的机会。下面程序使用 yield() 方法来让当前正在执行的线程暂停。

public class YieldTest extends Thread{
  public YieldTest(String name) {
    super(name);
  }
  // 定义run()方法作为线程执行体
  public void run() {
    for(int i=0;i<50;i++) {
      System.out.println(getName()+"  "+i);
      // 当 i等于20时,使用 yield() 方法让当前线程让步
      if(i==20) {
        Thread.yield();
      }
    }
  }
  public static void main(String[] args) {
    // 启动两个并发线程
    YieldTest yt1 = new YieldTest("高级");
    // 将yt1线程设置成最高优先级
    // yt1.setPriority(Thread.MAX_PRIORITY);
    yt1.start();
    YieldTest yt2 = new YieldTest("低级");
    // 将yt2线程设置成最低优先级
    // yt2.setPriority(Thread.MIN_PRIORITY);
    yt2.start();
  }
}

注意:在多 CPU 并行的环境下, yield() 方法的功能有时候并不明显。

关于 sleep() 方法和 yield() 方法的区别如下。

  • sleep() 方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级;但 yield() 方法只会给优先级相同,或优先级更高的线程执行机会。
  • sleep() 方法会将线程转入阻塞状态,直到经过阻塞时间才会转入就绪状态;而 yield() 不会将线程转入阻塞状态,它只是强制当前线程进入就绪状态。因此完全有可能某个线程调用 yield() 方法暂停之后,立即再次获得处理器资源被执行。
  • sleep() 方法声明抛出了 InterruptedException 异常,所以调用 sleep() 方法时要么捕捉该异常,要么显式声明抛出该异常;而 yield ()方法则没有声明抛出任何异常。
  • sleep() 方法比 yield() 方法有更好的可移植性,通常不建议使用 yield() 方法来控制并发线程的执行。

改变线程优先级

每个线程执行时都具有一定的优先级,优先级高的线程获得较多的执行机会,而优先级低的线程则获得较少的执行机会。

每个线程默认的优先级都与创建它的父线程的优先级相同,在默认情况下, main 线程具有普通优先级,由 main 线程创建的子线程也具有普通优先级。

Thread 类提供了 setPriority(int newPriority)、 getPriority() 方法来设置和返回指定线程的优先级,其中 setPriority() 方法的参数可以是一个整数,范围是1~10之间,也可以使用 Thread 类的如下三个静态常量。

  • MAX _ PRIORITY:其值是 10。
  • MIN _ PRIORITY:其值是 1 。
  • NORM _ PRIORITY:其值是 5。

下面程序使用了 setPriority() 方法来改变主线程的优先级,并使用该方法改变了两个线程的优先级,从而可以看到高优先级的线程将会获得更多的执行机会。

public class PriorityTest extends Thread{
  public PriorityTest(String name) {
    super(name);
  }
  public void run() {
    for(int i=0;i<50;i++) {
      System.out.println(getName()+",其优先级是:"+getPriority()+",循环变量的值为:"+i);
    }
  }
  public static void main(String[] args) {
    // 改变主线程的优先级
    Thread.currentThread().setPriority(6);
    for(int i=0;i<30;i++) {
      if(i==10) {
        PriorityTest low = new PriorityTest("低级");
        low.start();
        System.out.println("创建之初的优先级:"+low.getPriority());
        // 设置该线程为最低优先级
        low.setPriority(Thread.MIN_PRIORITY);
      }
      if(i==20) {
        PriorityTest high = new PriorityTest("高级");
        high.start();
        System.out.println("创建之初的优先级:"+high.getPriority());
        // 设置该线程为最高优先级
        high.setPriority(Thread.MAX_PRIORITY);
      }
    }
  }
}

上面程序中的第一行粗体字代码改变了主线程的优先级为6,这样由main线程所创建的子线程的优先级默认都是6,所以程序直接输出 low、high 两个线程的优先级时应该看到6。接着程序将 low 线程的优先级设为 Priority.MIN_PRIORITY,将 high 线程的优先级设置为 Priority.MAX_PRIORITY。

运行上面程序,会看到如下图所示的效果。

值得指出的是,虽然 Java 提供了 10 个优先级级别,但这些优先级级别需要操作系统的支持。遗憾的是,不同操作系统上的优先级并不相同,而且也不能很好地和 Java 的10个优先级对应,例如 Windows 2000 仅提供了 7个优先级。因此应该尽量避免直接为线程指定优先级,而应该使用 MAX_PRIORITY、MIN_PRIORITY 和 NORM_PRIORITY 三个静态常量来设置优先级,这样才可以保证程序具有最好的可移植性。

以上就是Java 控制线程的方法的详细内容,更多关于Java 控制线程的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java编程中实现Condition控制线程通信

    java中控制线程通信的方法 1.传统的方式:利用synchronized关键字来保证同步,结合wait(),notify(),notifyAll()控制线程通信.不灵活. 2.利用Condition控制线程通信,灵活. 3.利用管道pipe进行线程通信,不推荐 4.利用BlockingQueue控制线程通信 本文就讲解利用Condition控制线程通信,非常灵活的方式. Condition类是用来保持Lock对象的协调调用. 对Lock不了解的可以参考:Java线程同步Lock同步锁代码示例

  • java控制线程运行

    1.线程的控制很常见,如文件传送到一半时,需要暂停文件传送,或终止文件传送,这实际上就是控制线程的运行. 2.线程有创建.可运行.运行中.阻塞.死亡5个状态. 创建:使用new运算符创建一个线程 可运行:使用start方法启动一个线程后,系统分配了资源 运行中状态:执行线程的run方法 阻塞:运行的线程因为某种原因停止继续运行 死亡状态:线程结束 3.传统方法的安全问题 Thread的stop(),suspend(),resume(),destroy()方法,因为不安全,可能造成死锁,已经不再使

  • Java线程的控制详解

    1. join线程: 在线程执行过程中,有时想让另一个线程先执行,比如将一大问题分割成许多小问题,给每一个小问题分配线程,但所有小问题处理完后再让主线程进一步操作.此时我们可以在主线程中调用其它线程的join()方法,以阻塞调用线程(在这里为主线程). 示例代码: 复制代码 代码如下: package org.frzh.thread;    public class JoinThread extends Thread{      //提供一个有参构造器,用来设置线程的名字      public

  • java信号量控制线程打印顺序的示例分享

    复制代码 代码如下: import java.util.concurrent.Semaphore; public class ThreeThread { public static void main(String[] args) throws InterruptedException {  Semaphore sempA = new Semaphore(1);  Semaphore sempB = new Semaphore(0);  Semaphore sempC = new Semapho

  • Java线程的生命周期和状态控制_动力节点Java学院整理

    一.线程的生命周期 线程状态转换图: 1.新建状态 用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态.处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(runnable). 注意:不能对已经启动的线程再次调用start()方法,否则会出现Javalang.IllegalThreadStateException异常. 2.就绪状态 处于就绪状态的线程已经具备了运行条件,但还没有分配到CPU,处于线程就绪队列(尽管是采用队列形式,事实上,把它称

  • java线程池:获取运行线程数并控制线程启动速度的方法

    在java里, 我们可以使用Executors.newFixedThreadPool 来创建线程池, 然后就可以不停的创建新任务,并用线程池来执行了. 在提交任务时,如果线程池已经被占满,任务会进到一个队列里等待执行. 这种机制在一些特定情况下会有些问题.今天我就遇到一种情况:创建线程比线程执行的速度要快的多,而且单个线程占用的内存又多,所以很快内存就爆了. 想了一个办法,就是在提交任务之前,先检查目前正在执行的线程数目,只有没把线程池占满的时候在去提交任务. 代码很简单: int thread

  • Java中使用阻塞队列控制线程集实例

    队列以一种先进先出的方式管理数据.如果你试图向一个已经满了的阻塞队列中添加一个元素,或是从一个空的阻塞队列中移除一个元素,将导致线程阻塞.在多线程进行合作时,阻塞队列是很有用的工具.工作者线程可以定期的把中间结果存到阻塞队列中.而其他工作者线程把中间结果取出并在将来修改它们.队列会自动平衡负载.如果第一个线程集运行的比第二个慢,则第二个线程集在等待结果时就会阻塞.如果第一个线程集运行的快,那么它将等待第二个线程集赶上来. 下面的程序展示了如何使用阻塞队列来控制线程集.程序在一个目录及它的所有子目

  • Java join 线程控制用法

    JDK说明: joinpublic final void join()                throws InterruptedException等待该线程终止. 抛出:InterruptedException - 如果任何线程中断了当前线程.当抛出该异常时,当前线程的中断状态 被清除测试代码: 复制代码 代码如下: public class MyThread extends Thread { public static void main(String[] args) throws

  • 举例讲解Java中Piped管道输入输出流的线程通信控制

    PipedOutputStream和PipedInputStream 在java中,PipedOutputStream和PipedInputStream分别是管道输出流和管道输入流. 它们的作用是让多线程可以通过管道进行线程间的通讯.在使用管道通信时,必须将PipedOutputStream和PipedInputStream配套使用. 使用管道通信时,大致的流程是:我们在线程A中向PipedOutputStream中写入数据,这些数据会自动的发送到与PipedOutputStream对应的Pip

  • Java使用Condition控制线程通信的方法实例详解

    本文实例讲述了Java使用Condition控制线程通信的方法.分享给大家供大家参考,具体如下: 一 点睛 当使用Lock对象来保证同步时,Java提供了一个Condition类来保持协调,使用Condition可以让那些已经得到Lock对象.却无法继续执行的线程释放Lock对象,Condtion对象也可以唤醒其他处于等待的线程. Condition 将同步监视锁方法(wait.notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与Lock对象组合使用,为每个对象提供多

  • Java使用阻塞队列控制线程通信的方法实例详解

    本文实例讲述了Java使用阻塞队列控制线程通信的方法.分享给大家供大家参考,具体如下: 一 点睛 阻塞队列主要用在生产者/消费者的场景,下面这幅图展示了一个线程生产.一个线程消费的场景: 负责生产的线程不断的制造新对象并插入到阻塞队列中,直到达到这个队列的上限值.队列达到上限值之后生产线程将会被阻塞,直到消费的线程对这个队列进行消费.同理,负责消费的线程不断的从队列中消费对象,直到这个队列为空,当队列为空时,消费线程将会被阻塞,除非队列中有新的对象被插入. BlockingQueue的核心方法:

随机推荐