一聚教程网:一个值得你收藏的教程网站

热门教程

关于Java中停止线程执行的方法总结

时间:2022-06-29 02:52:26 编辑:袖梨 来源:一聚教程网

Java中停止线程执行的方法

一、暂停或停止线程的理论

在Java编程中,要暂停或停止当前正在运行的线程,有几种方法。对于把线程转入睡眠Sleep状态,使用Thread.sleep()是最正确的方式。或许有人会问,为什么不使用等待wait()或通知notify()?要知道,使用等待或通知都不是很好的方式。
线程可以使用等待wait()实现被阻塞,这属于条件等待的方式,当条件满足后,又会从阻塞转为等待状态。尽管可以在等待wait()条件那里放一个超时设置,但等待wait()的设计目的不是这样的,等待wait()在设计上是用于Java线程间的通信。
而使用睡眠sleep()方式,可以让线程从当前开始睡眠指定的时间。注意不要使用睡眠sleep()方式去代替等待wait()或通知notify(),反之亦然。
Java线程通信的例子: http://javarevisited.blogspot.sg/2013/12/inter-thread-communication-in-java-wait-notify-example.html
等待wait()或通知notify()不应该用于暂停线程,还有一个原因,等待wait()或通知notify()需要一个锁。只能从一个同步的方法或同步的代码块去调用它们,获取锁和释放锁的开销是比较大的。而且,只是暂停线程的话,无需引入锁机制。
sleep()与wait()还有一点不同,sleep()会把当前的线程转入等待状态,它不会释放它持有的任何锁,而wait()使得线程转入阻塞状态,会释放掉自己持有的锁。
总之,Java多线程编程并不简单,即使是简单的任务,如创建线程、停止线程或暂停线程,都需要认真掌握Java API。

二、暂停或停止线程的实战

下面的例子中,要暂停线程,可以使用Thread.sleep()或TimeUnit.sleep()方法。例子中,有两个线程,主线程由JVM启动,它执行main()方法。第二个线程叫T1,它由主线程创建,用于循环运行游戏。我们传递的Runnable任务是一个无限循环,会一直运行直到我们停止它。注意看,我使用了volatile关键字。
主线程首先启动T1线程,再使用stop()方法停止线程。
在这个例子中,我们有两种方法停止线程的运行,使用Thread.sleep()方法或者是使用TimeUnit.sleep()方法。TimeUnit类既可以指定秒即TimeUnit.SECONDS,又可以指定毫秒即TimeUnit.MILLISECONDS。总的来说,使用TimeUnit的sleep()方法,使得代码更为易读。

点击(此处)折叠或打开

import static java.lang.Thread.currentThread;
import java.util.concurrent.TimeUnit;
public class ThreadPauseDemo{
    public static void main(String args[]) throws InterruptedException {
        Game game = new Game();
        Thread t1 = new Thread(game, "T1");
        t1.start();
        // 现在停止Game线程
        System.out.println(currentThread().getName() + " is stopping game thread");
        game.stop();
        // 查看Game线程停止的状态
        TimeUnit.MILLISECONDS.sleep(200);
        System.out.println(currentThread().getName() + " is finished now");
    }
}


点击(此处)折叠或打开

class Game implements Runnable{
    private volatile boolean isStopped = false;
    public void run(){
        while(!isStopped){
            System.out.println("Game thread is running......");
            System.out.println("Game thread is now going to pause");
            try{
                Thread.sleep(200);
            } catch(InterruptedException e){
                e.printStackTrace();
            }
            System.out.println("Game thread is now resumed......");
        }
        System.out.println("Game thread is stopped......");
    }
    public void stop(){
        isStopped = true;
    }
}


程序输出如下:
Game thread is running......
main is stopping game thread
Game thread is now going to pause
Game thread is now resumed......
Game thread is stopped......
main is finished now

注:
volatile关键字:当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取这个值时,它会去主内存中读取新值。volatile关键字保证了可见性。普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。
一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么它就具备了两层语义:
1. 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2. 禁止进行指令重排序。
volatile关键字禁止指令重排序有两层意思:
1. 当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行。
2. 在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
volatile一般情况下不能代替sychronized,因为volatile不能保证操作的原子性,即使只是i++,实际上也是由多个原子操作组成:read i; inc; write i,假如多个线程同时执行i++,volatile只能保证他们操作的i是同一块内存,但依然可能出现写入脏数据的情况。

三、sleep()方法总结

Thread.sleep()方法可以让线程暂停或停止,它还有一些细节需要注意:
1. Thread.sleep()方法是一个静态方法,它总是可以让当前的线程转入睡眠。
2. 可以调用interrupt()方法把当前睡眠的线程唤醒。
3. sleep()方法不能保证线程能精准地在指定那一毫秒内转入睡眠,它的精度取决于系统的计时器。
4. 它不会释放它所获得的锁。




如何停止java线程

如何停止java的线程一直是一个困恼我们开发多线程程序的一个问题。这个问题最终在Java5的java.util.concurrent中得到了回答:使用interrupt(),让线程在run方法中停止。
简介

在Java的多线程编程中,java.lang.Thread类型包含了一些列的方法start(), stop(), stop(Throwable) and suspend(), destroy() and resume()。通过这些方法,我们可以对线程进行方便的操作,但是这些方法中,只有start()方法得到了保留。

在Sun公司的一篇文章《Why are Thread.stop, Thread.suspend and Thread.resume Deprecated? 》中详细讲解了舍弃这些方法的原因。那么,我们究竟应该如何停止线程呢?


建议使用的方法

在《Why are Thread.stop, Thread.suspend and Thread.resume Deprecated? 》中,建议使用如下的方法来停止线程:

    private volatile Thread blinker;
    public void stop() {
        blinker = null;
    }
    public void run() {
        Thread thisThread = Thread.currentThread();
        while (blinker == thisThread) {
            try {
                thisThread.sleep(interval);
            } catch (InterruptedException e){
            }
            repaint();
        }
    }


关于使用volatile关键字的原因,请查看http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#36930。
当线程处于非运行(Run)状态

当线程处于下面的状况时,属于非运行状态:

    当sleep方法被调用。

    当wait方法被调用。

    当被I/O阻塞,可能是文件或者网络等等。

当线程处于上述的状态时,使用前面介绍的方法就不可用了。这个时候,我们可以使用interrupt()来打破阻塞的情况,如:

public void stop() {
        Thread tmpBlinker = blinker;
        blinker = null;
        if (tmpBlinker != null) {
           tmpBlinker.interrupt();
        }
    }


当interrupt()被调用的时候,InterruptedException将被抛出,所以你可以再run方法中捕获这个异常,让线程安全退出:

try {
   ....
   wait();
} catch (InterruptedException iex) {
   throw new RuntimeException("Interrupted",iex);
}


阻塞的I/O

当线程被I/O阻塞的时候,调用interrupt()的情况是依赖与实际运行的平台的。在Solaris和Linux平台上将会抛出InterruptedIOException的异常,但是Windows上面不会有这种异常。所以,我们处理这种问题不能依靠于平台的实现。如:

package com.cnblogs.gpcuster
import java.net.*;
import java.io.*;
public abstract class InterruptibleReader extends Thread {
    private Object lock = new Object( );
    private InputStream is;
    private boolean done;
    private int buflen;
    protected void processData(byte[] b, int n) { }
    class ReaderClass extends Thread {
        public void run( ) {
            byte[] b = new byte[buflen];
            while (!done) {
                try {
                    int n = is.read(b, 0, buflen);
                    processData(b, n);
                } catch (IOException ioe) {
                    done = true;
                }
            }
            synchronized(lock) {
                lock.notify( );
            }
        }
    }
    public InterruptibleReader(InputStream is) {
        this(is, 512);
    }
    public InterruptibleReader(InputStream is, int len) {
        this.is = is;
        buflen = len;
    }
    public void run( ) {
        ReaderClass rc = new ReaderClass( );
        synchronized(lock) {
            rc.start( );
            while (!done) {
                try {
                    lock.wait( );
                } catch (InterruptedException ie) {
                    done = true;
                    rc.interrupt( );
                    try {
                        is.close( );
                    } catch (IOException ioe) {}
                }
            }
        }
    }
}



另外,我们也可以使用InterruptibleChannel接口。 实现了InterruptibleChannel接口的类可以在阻塞的时候抛出ClosedByInterruptException。如:

package com.cnblogs.gpcuster
import java.io.BufferedReader;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.channels.Channels;
public class InterruptInput {   
    static BufferedReader in = new BufferedReader(
            new InputStreamReader(
            Channels.newInputStream(
            (new FileInputStream(FileDescriptor.in)).getChannel())));
    
    public static void main(String args[]) {
        try {
            System.out.println("Enter lines of input (user ctrl+Z Enter to terminate):");
            System.out.println("(Input thread will be interrupted in 10 sec.)");
            // interrupt input in 10 sec
            (new TimeOut()).start();
            String line = null;
            while ((line = in.readLine()) != null) {
                System.out.println("Read line:'"+line+"'");
            }
        } catch (Exception ex) {
            System.out.println(ex.toString()); // printStackTrace();
        }
    }
    
    public static class TimeOut extends Thread {
        int sleepTime = 10000;
        Thread threadToInterrupt = null;    
        public TimeOut() {
            // interrupt thread that creates this TimeOut.
            threadToInterrupt = Thread.currentThread();
            setDaemon(true);
        }
        
        public void run() {
            try {
                sleep(10000); // wait 10 sec
            } catch(InterruptedException ex) {/*ignore*/}
            threadToInterrupt.interrupt();
        }
    }
}


这里还需要注意一点,当线程处于写文件的状态时,调用interrupt()不会中断线程。



如何正确停止java中的线程

为什么不能使用Thread.stop()方法?

从SUN的官方文档可以得知,调用Thread.stop()方法是不安全的,这是因为当调用Thread.stop()方法时,会发生下面两件事:

1. 即刻抛出ThreadDeath异常,在线程的run()方法内,任何一点都有可能抛出ThreadDeath Error,包括在catch或finally语句中。
2. 释放该线程所持有的所有的锁


当线程抛出ThreadDeath异常时,会导致该线程的run()方法突然返回来达到停止该线程的目的。ThreadDetath异常可以在该线程run()方法的任意一个执行点抛出。但是,线程的stop()方法一经调用线程的run()方法就会即刻返回吗?

public static void main(String[] args) {   
        try {   
            Thread t = new Thread() {   
                public synchronized void run() {   
                    try {   
                        long start=System.currentTimeMillis();   
                        for (int i = 0; i < 100000; i++)   
                            System.out.println("runing.." + i);   
                        System.out.println((System.currentTimeMillis()-start)/1000);   
                    } catch (Throwable ex) {   
                        System.out.println("Caught in run: " + ex);   
                        ex.printStackTrace();   
                    }   
                }   
            };   
            t.start();   
            // Give t time to get going...   
            Thread.sleep(100);   
            t.stop(); // EXPECT COMPILER WARNING   
        } catch (Throwable t) {   
            System.out.println("Caught in main: " + t);   
            t.printStackTrace();   
        }   
  
    }  
public static void main(String[] args) {  
        try {  
            Thread t = new Thread() {  
                public synchronized void run() {  
                    try {  
                        long start=System.currentTimeMillis();  
                        for (int i = 0; i < 100000; i++)  
                            System.out.println("runing.." + i);  
                        System.out.println((System.currentTimeMillis()-start)/1000);  
                    } catch (Throwable ex) {  
                        System.out.println("Caught in run: " + ex);  
                        ex.printStackTrace();  
                    }  
                }  
            };  
            t.start();  
            // Give t time to get going...  
            Thread.sleep(100);  
            t.stop(); // EXPECT COMPILER WARNING  
        } catch (Throwable t) {  
            System.out.println("Caught in main: " + t);  
            t.printStackTrace();  
        }  
  
    }


假设我们有如上一个工作线程,它的工作是数数,从1到1000000,我们的目标是在它进行数数的过程中,停止该线程的运作。如果我们按照上面的方式来调用thread.stop()方法,原则上是可以实现我们的目标的,根据SUN官方文档的解释,加上在上面的程序中,主线程只休眠了100ms,而工作线程从1数到1000000所花时间大概是4-5s,那么该工作线程应该只从1数到某个值(小于1000000),然后线程停止。

但是根据运行结果来看,并非如此。

结果:

。。。

runing..99998
runing..99999

5

。。。

runing..99998
runing..99999
4

每次运行的结果都表明,工作线程并没有停止,而是每次都成功的数完数,然后正常中止,而不是由stop()方法进行终止的。这个是为什么呢?根据SUN的文档,原则上只要一调用thread.stop()方法,那么线程就会立即停止,并抛出ThreadDeath error,查看了Thread的源代码后才发现,原先Thread.stop0()方法是同步的,而我们工作线程的run()方法也是同步,那么这样会导致主线程和工作线程共同争用同一个锁(工作线程对象本身),由于工作线程在启动后就先获得了锁,所以无论如何,当主线程在调用t.stop()时,它必须要等到工作线程的run()方法执行结束后才能进行,结果导致了上述奇怪的现象。

把上述工作线程的run()方法的同步去掉,再进行执行,结果就如上述第一点描述的那样了

可能的结果:

runing..4149
runing..4150
runing..4151
runing..4152runing..4152Caught in run: java.lang.ThreadDeath

或者

runing..5245
runing..5246
runing..5247
runing..5248runing..5248Caught in run: java.lang.ThreadDeath


接下来是看看当调用thread.stop()时,被停止的线程会不会释放其所持有的锁,看如下代码:


public static void main(String[] args) {   
        final Object lock = new Object();   
        try {   
            Thread t0 = new Thread() {   
                public void run() {   
                    try {   
                        synchronized (lock) {   
                            System.out.println("thread->" + getName()   
                                    + " acquire lock.");   
                            sleep(3000);// sleep for 3s   
                            System.out.println("thread->" + getName()   
                                    + " release lock.");   
                        }   
                    } catch (Throwable ex) {   
                        System.out.println("Caught in run: " + ex);   
                        ex.printStackTrace();   
                    }   
                }   
            };   
  
            Thread t1 = new Thread() {   
                public void run() {   
                    synchronized (lock) {   
                        System.out.println("thread->" + getName()   
                                + " acquire lock.");   
                    }   
                }   
            };   
  
            t0.start();   
            // Give t time to get going...   
            Thread.sleep(100);   
            //t0.stop();   
            t1.start();   
        } catch (Throwable t) {   
            System.out.println("Caught in main: " + t);   
            t.printStackTrace();   
        }   
  
    }  
        
public static void main(String[] args) {  
    final Object lock = new Object();  
    try {  
        Thread t0 = new Thread() {  
            public void run() {  
                try {  
                    synchronized (lock) {  
                        System.out.println("thread->" + getName()  
                                + " acquire lock.");  
                        sleep(3000);// sleep for 3s  
                        System.out.println("thread->" + getName()  
                                + " release lock.");  
                    }  
                } catch (Throwable ex) {  
                    System.out.println("Caught in run: " + ex);  
                    ex.printStackTrace();  
                }  
            }  
        };  
        Thread t1 = new Thread() {  
            public void run() {  
                synchronized (lock) {  
                    System.out.println("thread->" + getName()  
                            + " acquire lock.");  
                }  
            }  
        };  
        t0.start();  
        // Give t time to get going...  
        Thread.sleep(100);  
        //t0.stop();  
        t1.start();  
    } catch (Throwable t) {  
        System.out.println("Caught in main: " + t);  
        t.printStackTrace();  
    }  
}



当没有进行t0.stop()方法的调用时, 可以发现,两个线程争用锁的顺序是固定的。

输出:

thread->Thread-0 acquire lock.
thread->Thread-0 release lock.
thread->Thread-1 acquire lock.


但调用了t0.stop()方法后,(去掉上面的注释//t0.stop();),可以发现,t0线程抛出了ThreadDeath error并且t0线程释放了它所占有的锁。


输出:

thread->Thread-0 acquire lock.
thread->Thread-1 acquire lock.
Caught in run: java.lang.ThreadDeath
java.lang.ThreadDeath
 at java.lang.Thread.stop(Thread.java:715)
 at com.yezi.test.timeout.ThreadStopTest.main(ThreadStopTest.java:40)

从上面的程序验证结果来看,thread.stop()确实是不安全的。它的不安全主要是针对于第二点:释放该线程所持有的所有的锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用thread.stop()后导致了该线程所持有的所有锁的突然释放,那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。


如何正确停止线程

关于如何正确停止线程,这篇文章(how to stop thread)给出了一个很好的答案, 总结起来就下面3点(在停止线程时):

1. 使用violate boolean变量来标识线程是否停止
2. 停止线程时,需要调用停止线程的interrupt()方法,因为线程有可能在wait()或sleep(), 提高停止线程的即时性
3. 对于blocking IO的处理,尽量使用InterruptibleChannel来代替blocking IO
 

核心如下:

If you are writing your own small thread then you should follow the following example code.
private volatile Thread myThread;
public void stopMyThread() {
    Thread tmpThread = myThread;
    myThread = null;
    if (tmpThread != null) {
        tmpThread.interrupt();
    }
}
public void run() {
    if (myThread == null) {
       return; // stopped before started.
    }
    try {
        // all the run() method's code goes here
        ...
        // do some work
        Thread.yield(); // let another thread have some time perhaps to stop this one.
        if (Thread.currentThread().isInterrupted()) {
           throw new InterruptedException("Stopped by ifInterruptedStop()");
        }
        // do some more work
        ...
    } catch (Throwable t) {
       // log/handle all errors here
    }
}


热门栏目