如何中止Java线程

    使用Java内置支持的线程写多线程程序是很常见的事情。然而,多线程给开发人员带来了一些新的挑战。如果处理不好就会导致超出预期的行为以及难于定位的错误。这篇文章解读了其中一个挑战:如何中止一个正在运行的线程。

 

背景

    中止一个线程意味着在线程处理完任务之前停掉正在做的操作,特别是放弃当前的操作。之后无论线程死掉,等待新的任务,或者执行下一步取决于应用程序的逻辑。

    虽然这看起来非常简单,但是你必须做好防范措施,以便达到预期的效果。而且有些提醒你必须注意。

    首先,忘记Thread.stop 方法吧。虽然他确实可以停掉一个正在运行的线程,但是这个方法是不安全的(unsafe)且是被弃用的(deprecated)。这意味着在将来的Java版本中,这个方法将不可用。

    另一个容易迷惑的方法是Thread.interrupt。尽管名字是中止,但这个方法不会终止一个正在运行的线程(后面还会分析)。例1描述这个现象。这个例子中创建了一个线程,然后尝试使用Thread.interrupt去停止线程。例子中Thread.sleep留出充足时间供线程初始化和终止。线程本身不做任何有用的事情。

例1: Stopping a thread with Thread.interrupt()

class Example1 extends Thread {

  public static void main( String args[] ) throws Exception {

    Example1 thread = new Example1();

   System.out.println( "Starting thread..." );

   thread.start();

   Thread.sleep( 3000 );

   System.out.println( "Interrupting thread..." );

   thread.interrupt();

   Thread.sleep( 3000 );

   System.out.println( "Stopping application..." );

   System.exit( 0 );

  }

 

  public void run() {

    while ( true ) {

     System.out.println( "Thread is running..." );

      long time = System.currentTimeMillis();

      while ( System.currentTimeMillis()-time < 1000 ) {

      }

    }

  }

}
如果你运行例1的代码,你会在终端上看到下面的东西:

 Starting thread...
Thread is running...
Thread is running...
Thread is running...
Interrupting thread...
Thread is running...
Thread is running...
Thread is running...
Stopping application...

 

即使Thread.interrupt()被调用后,线程仍然会继续运行一段时间。

 

真正地中止一个线程

    最好的,也是推荐的中止线程的方式是使用一个共享变量去通知线程停掉正在做的事情。线程必须周期性地检测这个变量,特别是执行时间较长操作时,然后有序地停掉当前的任务。例2给出了这种技术。

例2: Signaling a thread to stop

class Example2 extends Thread {

  volatile boolean stop = false;

 

  public static void main( String args[] ) throws Exception {

    Example2 thread = new Example2();

   System.out.println( "Starting thread..." );

   thread.start();

   Thread.sleep( 3000 );

   System.out.println( "Asking thread to stop..." );

   thread.stop = true;

   Thread.sleep( 3000 );

   System.out.println( "Stopping application..." );

   System.exit( 0 );

  }

 

  public void run() {

    while ( !stop ) {

     System.out.println( "Thread is running..." );

      long time = System.currentTimeMillis();

      while ( (System.currentTimeMillis()-time < 1000) && (!stop) ) {

      }

    }

   System.out.println( "Thread exiting under request..." );

  }

}

运行例2的代码会得到如下的输出(注意线程如何以预期的方式结束):

 Starting thread...
Thread is running...
Thread is running...
Thread is running...
Asking thread to stop...
Thread exiting under request...
Stopping application...

 

     虽然这个方法需要编写一些代码,但并不难实现,且给了线程处理任何清理工作的机会。这对任何多线程应用来说都是绝对需要的。注意一定要把共享变量设置为volatile,或者将访问他的操纵封装到synchronized代码块/方法中。

    到现在为止,实现的已经很好了。但是如果线程被阻塞住等待一些事件呢?显然如果线程被阻塞,就不能检测共享变量,也就不能停掉。很多场景会导致线程被阻塞,举几个例子,如调用Object.wait()ServerSocket.accept(),及DatagramSocket.receive()等。

     他们都可以一直阻塞线程。即使设置了超时时间,等待超时过期也未必是可行的或期望的结果。所以必须使用一种可以提前退出阻塞状态的机制。

     然后并不存在适用所有场景的机制,特定的技术只能用在特定的场景下。下面的篇幅中,我会给出大部分常见场景的方案。

 

使用Thread.interrupt()终止线程

正如例1中的描述,Thread.interrupt()方法并不能中止正在运行的线程。这个方法真正的效果是如果线程被阻塞,则抛出一个中止异常,这样线程退出阻塞状态。更确切的说,如果被阻塞在Object.waitThread.join或者Thread.sleep其中一个方法上,线程会收到一个InterruptedException异常,这样提前结束阻塞线程的方法。

     所以,如果线程被上述的某个方法阻塞住,正确的终止方法是先设置共享变量,然后调用interrupt()方法(注意一定要先设置变量)。如果线程没有被阻塞,则调用interrupt()没有坏处;否则线程会获取一个异常(线程必须准备好处理这个场景)并推出阻塞状态。无论哪种情况下,最后线程会检测共享变量并终止。例3是描述这种技术的一个简单例子。

例3: Exiting blocked states with Thread.interrupt()

class Example3 extends Thread {

  volatile boolean stop = false;

 

  public static void main( String args[] ) throws Exception {

   Example3 thread = new Example3();

   System.out.println( "Starting thread..." );

   thread.start();

   Thread.sleep( 3000 );

   System.out.println( "Asking thread to stop..." );

   thread.stop = true;

   thread.interrupt();

   Thread.sleep( 3000 );

   System.out.println( "Stopping application..." );

   System.exit( 0 );

  }

 

  public void run() {

    while ( !stop ) {

     System.out.println( "Thread running..." );

      try {

      Thread.sleep( 1000 );

      } catch ( InterruptedException e ) {

      System.out.println( "Thread interrupted..." );

      }

    }

   System.out.println( "Thread exiting under request..." );

  }

}

例3中,一旦 Thread.interrupt()被调用,线程就会捕获一个异常,这样他就退出阻塞状态并判断出需要结束。运行这段代码会得到如下输出:

 Starting thread...
Thread running...
Thread running...
Thread running...
Asking thread to stop...
Thread interrupted...
Thread exiting under request...
Stopping application...

 

中止I/O操作

    但是如果线程阻塞在I/O操作上会怎样呢?I/O可能阻塞一个线程很长一段时间,特别是涉及网络通讯。例如,一个server在等待请求,或者一个网络应用在等待远端服务器的应答。

     如果你在使用Java 1.4中引入的新I/O API,被阻塞的线程会得到一个ClosedByInterruptException异常。这种情况下,逻辑上与第三个例子相同,只是异常不同。

     由于新 I/O最近才引入且需要更多的工作,你可能还在使用Java 1.0引入的传统I/O。这种情况下,Thread.interrupt()没有效果,所以线程不会退出阻塞状态。例4说明了这种行为。虽然interrupt()被调用,但线程没有退出阻塞状态。

 

例4: Interrupting I/O operations with Thread.interrupt()

import java.io.*;

 
class Example4 extends Thread {

 

  public static void main( String args[] ) throws Exception {

    Example4 thread = new Example4();

   System.out.println( "Starting thread..." );

   thread.start();

   Thread.sleep( 3000 );

   System.out.println( "Interrupting thread..." );

   thread.interrupt();

   Thread.sleep( 3000 );

   System.out.println( "Stopping application..." );

   System.exit( 0 );

  }

 

  public void run() {

   ServerSocket socket;

    try {

      socket = new ServerSocket(7856);

    } catch ( IOException e ) {

     System.out.println( "Could not create the socket..." );

      return;

    }

    while ( true ) {

     System.out.println( "Waiting for connection..." );

      try {

       Socket sock = socket.accept();

      } catch ( IOException e ) {

      System.out.println( "accept() failed or interrupted..." );

      }

    }

  }

}

    幸运的是,Java平台为这种场景提供了一个方案,即调用阻塞线程的socket的close()方法。这样线程会得到一个SocketExcpetion异常,类似于interrupt()导致抛出InterruptedException()

     唯一要注意的是socket的引用必须可以获取,以调用close()方法。也意味着socket对象必须也是共享的。例5描述了这种场景,逻辑跟目前为止给出的例子一样。

例5: Causing I/O operations to fail

import java.net.*;

import java.io.*;

 

class Example5 extends Thread {

  volatile boolean stop = false;

  volatile ServerSocket socket;

 

  public static void main( String args[] ) throws Exception {

    Example5 thread = new Example5();

   System.out.println( "Starting thread..." );

   thread.start();

   Thread.sleep( 3000 );

   System.out.println( "Asking thread to stop..." );

   thread.stop = true;

   thread.socket.close();

   Thread.sleep( 3000 );

   System.out.println( "Stopping application..." );

   System.exit( 0 );

  }

 

  public void run() {

    try {

      socket = new ServerSocket(7856);

    } catch ( IOException e ) {

     System.out.println( "Could not create the socket..." );

      return;

    }

    while ( !stop ) {

     System.out.println( "Waiting for connection..." );

      try {

       Socket sock = socket.accept();

      } catch ( IOException e ) {

      System.out.println( "accept() failed or interrupted..." );

      }

    }

   System.out.println( "Thread exiting under request..." );

  }

}

这是运行例5的期望输出:

 Starting thread...
Waiting for connection...
Asking thread to stop...
accept() failed or interrupted...
Thread exiting under request...
Stopping application...

 

     多线程是个强大的工具,但也带来了自己的挑战。其中一个就是如果中止正在运行的线程。如果合理的实现,这些技术可以使得中止一个线程和使用Java平台已经提供的内置操作一样简单。


原文:http://www.techrepublic.com/article/interrupting-java-threads/5144546


### 回答1: Java中的线程需要注意以下几点: 1. 同步问题:Java线程可能会相互影响,因此需要注意同步问题。可以使用synchronized关键字、Lock、ReadWriteLock等机制来解决。 2. 竞争条件:Java线程可能会导致竞争条件,因此需要注意避免竞争条件。 3. 线程安全:Java线程对共享数据的访问可能会导致线程安全问题,因此需要注意保证线程安全。 4. 线程生命周期:Java线程的生命周期可能会导致线程的意外终止,因此需要注意管理线程的生命周期。 5. 异常处理:Java线程可能会抛出异常,因此需要注意处理线程中的异常。 6. 线程调度:Java线程的调度方式可能会导致线程执行的顺序不确定,因此需要注意线程调度。 总的来说,在使用Java线程时,需要注意同步、竞争条件、线程安全、线程生命周期、异常处理和线程调度等问题。 ### 回答2: 在使用Java线程时,有几个重要的注意事项需要牢记。 首先,要注意线程的安全性。多个线程在共享数据时可能引发竞态条件和内存不一致的问题。为了确保线程安全性,可以使用锁机制来保护共享资源,或者使用线程安全的数据结构和类。 其次,要避免死锁。死锁指的是多个线程互相等待对方释放资源而无法继续执行的情况。为了避免死锁,可以按照相同的顺序获取锁,避免嵌套锁,或者使用定时锁等技术手段。 此外,要注意线程间的通信。线程之间可以通过共享变量或者消息队列等方式进行通信。在使用共享变量时,要确保对共享变量的访问是原子的,并且要使用适当的同步机制来保证可见性和有序性。而在使用消息队列时,要确保线程间的同步和协调,避免出现数据丢失或混乱的情况。 同时,要小心处理线程的异常。线程中出现的异常如果没有正确处理可能会导致线程中止或者程序崩溃。因此需要使用try-catch语句或者使用UncaughtExceptionHandler来处理线程中的异常。 最后,要合理地管理和控制线程的数量和生命周期。过多的线程会造成资源的浪费和性能的下降,而生命周期管理可以确保线程的正确启动、暂停和终止,并释放占用的资源。 总之,使用Java线程需要注意线程安全性、死锁、线程通信、异常处理以及线程数量和生命周期管理等问题,这些都是保证线程运行稳定和高效的重要方面。 ### 回答3: Java线程的运用是多线程编程中的关键部分,同时也是一个相对复杂的概念。在使用Java线程时,我们需要注意以下几点: 1. 同步:在多线程编程中,可能会出现资源竞争的问题,即多个线程同时访问和修改同一个数据。为了避免数据不一致或者数据损坏的情况,需要对关键代码块进行同步处理,通过synchronized关键字或者Lock对象来保证同一时间只有一个线程在执行关键代码块。 2. 锁:Java线程提供了锁机制来控制对共享资源的访问。在使用锁时,需要遵循几个原则:使用粒度最小的锁,避免死锁(相互等待对方释放锁的情况),以及适时释放锁。 3. 线程安全:保证在多线程环境下程序的正确性和一致性,需要确保共享对象的线程安全。可以通过使用线程安全的数据结构、使用volatile关键字来保证共享变量的可见性、使用原子类来保证原子操作等方式实现。 4. 线程调度:Java提供了多线程调度机制,可以通过控制线程的优先级、yield()方法、sleep()方法等来进行线程调度。需要注意确保线程调度的合理性,以避免出现饥饿和死锁的情况。 5. 异常处理:在多线程编程中,线程之间的异常不能互相影响,因此需要适当处理异常,避免线程因为异常而终止。 6. 内存管理:多线程编程可能会引发内存泄漏、内存溢出等问题,需要合理管理线程的内存。包括使用合适的数据结构、销毁不再使用的对象以及及时释放资源等。 总之,使用Java线程编程要注意保证线程安全、避免资源竞争、合理调度线程、正确处理异常和合理管理内存等。只有在考虑到这些问题并且合理处理时,才能确保多线程程序的正确性和性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值