Java多线程中,Join和Interrupt()方法的使用

更多详细的解答请转至:http://my.oschina.net/summerpxy/blog/198457;http://uule.iteye.com/blog/1101994;(比如有一个线程t.当在Main线程中调用t.join()的时候,那么Main线程必须拿到t线程的锁。否则,Main线程是无法wait()的。join()其实是和wai()联系在一起的。

一个线程的启动比较的简单,就是start()方法就可以了。但是在结束这个线程的时候,不能够使用stop()方法。一般情况下,我们会使用Interrupt()方法。

在此之前我想说明的一点就是,当我们调用interrupt()方法的时候,我们只是给这个线程设定了一个中断的标准,而至于中断还是不中断,需要线程本身进行处理。这是要注意的。当然我们也可以通过检查是否线程是否处于中断状态来避免这个问题。比如说,如果这个线程本身是处于中断状态的,那么久设定这个线程不能休眠,这是要注意的。中断和挂起是俩个不同的概念,这是要注意的。
/*当一个线程调用Interrupt()方法的时候,那么就会给这个线程设定一个标志,表明这个线程已经被中断了。
* 但是,异常在被捕获的时候,就会清理已经被中断了的这个标志。所以在catch子句中,在异常被捕获的时候,这个标志
* 总是为假的。除了异常之外,这个标志还可以用于其他的情况,比如线程可能会检查它的中断的状态。*/
class Sleeper extends  Thread{
    private int duration;

    public Sleeper(String name, int sleepTime) {
        super(name);
        duration=sleepTime;
        start();
    }

    @Override
    public void run() {
       try {
//           当调用这个sleep()方法的时候,那么这个线程可能会休眠指定的时间,但是也可能,在休眠的期间
//           还没有够的时候,就已经被中断了,那么try--catch就会捕获这个异常。
            sleep(duration);
        } catch (InterruptedException e) {
            System.out.println(getName() + "was interrupted." + "isInterrupted():" + isInterrupted());
            return;
        }
        System.out.println(getName() + "has awakened");
    }
}

class Joiner extends  Thread{
    private Sleeper sleeper;

    public Joiner(String name, Sleeper sleeper) {
        super(name);
        this.sleeper=sleeper;
        start();
    }

    @Override
    public void run() {
        try {
            sleeper.join();
        } catch (InterruptedException e) {
            System.out.println("Interrupted");
        }

        System.out.println(getName() + "join completed");
    }
}
public class Joining {
    public static void main(String[] args) {
        Sleeper sleepy = new Sleeper("Sleepy", 1500);
        Sleeper grumpy = new Sleeper("Grumpy", 1500);
        Joiner dopey = new Joiner("Dopey", sleepy);
        Joiner doc = new Joiner("Doc", grumpy);
        grumpy.interrupt();
    }
}
 
这个程序的输出结果如下:
Grumpywas interrupted.isInterrupted():false
Docjoin completed
Sleepyhas awakened
Dopeyjoin completed
1.join的用法:如果有俩个线程A和B.在A线程中调用B.join().那么当前线程,也就是A线程会被挂起,暂停执行。一直等到B执行完毕以后,A线程在继续执行。
2.如果有俩个线程A和B。在A线程中调用B.join(1000).那么当前线程,也就是A线程,就会被挂起1000ms.在A挂起的过程当中,B线程会执行。B线程执行了1000ms以后,A线程在继续执行。
3调用Thread.sleep()方法的时候,如果当前线程处于中断那状态,那么sleep()方法不会执行,同时会清除掉该状态,并且抛出interruptedException异常。上面的一个例子中说明的就是这个问题。
现在我们再来深入学习一下interrupt()方法。

interrupt字面上是中断的意思,但在Java里Thread.interrupt()方法实际上通过某种方式通知线程,并不会直接中止该线程。具体做什么事情由写代码的人决定,通常我们会中止该线程。

    如果线程在调用Object类的wait()、wait(long)或wait(long, int)方法,或者该类的 join() 、join(long) 、join(long, int) 、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException。

    如果该线程在可中断的通道(java.nio.channels.InterruptibleChannel)上的 I/O 操作中受阻,则该通道将被关闭,该线程的中断状态将被设置并且该线程将收到一个 ClosedByInterruptException。

    如果该线程在一个 Selector (java.nio.channels.Selector) 中受阻,则该线程的中断状态将被设置,它将立即从选择操作返回,并可能带有一个非零值,就好像调用了选择器的 wakeup 方法一样。 

在java中,开启一个多线程是很简单的,只需要new一个runnable就可以了,但是要停止一个线程,却不能简单的使用Thread.stop()方法。

  首先来说说java中的中断机制,Java中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理中断。当调用interrupt()方法的时候,只是设置了要中断线程的中断状态,而此时被中断的线程的可以通过isInterrupted()或者是interrupted()方法判断当前线程的中断状态是否标志为中断。我们可以从interrupt()方法来看:

  public void interrupt() {
    if (this != Thread.currentThread())
        checkAccess();

    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
        interrupt0();		// Just to set the interrupt flag
        b.interrupt();
        return;
        }
    }
    interrupt0();
    }

从这个方法中我们可以看到,最直接的调用时interrupt0()这个方法,而这个方法仅仅是设置了线程中断状态。

  我们再看看isInterrupted()方法:

  public boolean isInterrupted() {
    return isInterrupted(false);
    }

    /**
     * Tests if some Thread has been interrupted.  The interrupted state
     * is reset or not based on the value of ClearInterrupted that is
     * passed.
     */
    private native boolean isInterrupted(boolean ClearInterrupted);

从这个方法中,我们可以猜测到,isInterrupted()方法仅仅是检查了当前线程的中断状态,但是不会清除这个状态。


我们再来看看静态方法interrupted()

 public static boolean interrupted() {
    return currentThread().isInterrupted(true);
    }

这个方法同样是检测当前线程的中断状态,但是这个方法会产生一个副作用,就是会清除当前线程的中断状态。


Thread.interrupt()  VS  Thread.stop()

 这两个方法最大的区别在于:interrupt()方法是设置线程的中断状态,让用户自己选择时间地点去结束线程;而stop()方法会在代码的运行处直接抛出一个ThreadDeath错误,这是一个java.lang.Error的子类。所以直接使用stop()方法就有可能造成对象的不一致性。


调用Thread.sleep()方法的时候,如果当前线程处于中断那状态,那么sleep()方法不会执行,同时会清除掉该状态,并且抛出interruptedException异常。

中断的使用demo:

package com.app.basic;

public class InterruptTest {

    
    public static void main(String[] args) {
        

        Runnable runnable1 = new Runnable() {

            @Override
            public void run() {

                for (int i = 0; i < 10; i++) {

                    System.out.println(i);

                    if (Thread.currentThread().isInterrupted()) {
                        System.out.println("aa");
                        break;
                    }
                    try {
                        
                        Thread.sleep(1000);

                    } catch (InterruptedException e) {

                        e.printStackTrace();
                       //sleep方法抛出这个异常之后会清除中断状态,所以需要重新设置中断状态
                        Thread.currentThread().interrupt();
                    }
                }
            }

        };

        final Thread t1 = new Thread(runnable1);

        Runnable runnable2 = new Runnable() {

            @Override
            public void run() {

                try {

                    Thread.sleep(3000);

                    t1.interrupt();

                } catch (InterruptedException e) {

                    e.printStackTrace();
                }

            }

        };

        Thread t2 = new Thread(runnable2);

        t1.start();
        t2.start();
    }

}
   
   
我们知道现在在concurrent类库中,已经避免了在对Thread对象的直接的操作,而是通过Executor来进行操作。在我们已经启动了线程以后,如果我们要终止线程的执行,那么我们可以通过调用shutdownNow()方法来进行。那么现在我们来聊一下这个方法是如何进行工作的。当你调用这个方法的时候,那么就会发送一个interrupt()方法给所有的线程,当然这么做是有意义的。现在我们来看一下各个方法之间是怎么工作的。
package Concurrency;
public class InterruptTest {
    public static void main(String[] args) {
        Runnable runnable1 = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(i);
                    if (Thread.currentThread().isInterrupted()) {
                        System.out.println("aa");
                        break;
                    }
                    try { 
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                       //sleep方法抛出这个异常之后会清除中断状态,所以需要重新设置中断状态
                     Thread.currentThread().interrupt();
                    }
                }
            }
        };
        final Thread t1 = new Thread(runnable1);
        Runnable runnable2 = new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);//这里的目的是先让线程A执行一段时间。
                    t1.interrupt();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread t2 = new Thread(runnable2);
        t1.start();
        t2.start();
    }
}
最后的输0
1
2
java.lang.InterruptedException: sleep interrupted
3
aa
at java.lang.Thread.sleep(Native Method)
at Concurrency.InterruptTest$1.run(InterruptTest.java:24)
at java.lang.Thread.run(Unknown Source)出的结果为:

但是如果把try-catch中的内容清除掉的话,即就是下面的内容:
  try { 
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                       //sleep方法抛出这个异常之后会清除中断状态,所以需要重新设置中断状态
                     //Thread.currentThread().interrupt();
                    }
那么最后的输出的结果为:
0
1
2
java.lang.InterruptedException: sleep interrupted
3
	at java.lang.Thread.sleep(Native Method)
	at Concurrency.InterruptTest$1.run(InterruptTest.java:24)
	at java.lang.Thread.run(Unknown Source)
4
5
6
7
8
9
如果把run中的内容改为如下的内容的话,那么输出的结果又会是怎么样的呢?
 Runnable runnable1 = new Runnable() {


            @Override
            public void run() {


                for (int i = 0; i < 10; i++) {


                    System.out.println(i);

//这里做了小小的改变。如果调用的是Thread.interrupted()这个方法的话,那么将会产生的一个副作用就是
//这个将会清除线程的中断的状态。
                    if (Thread.interrupted()) {
                        System.out.println("aa");
                        break;
                    }
                    try {
                        
                        Thread.sleep(1000);


                    } catch (InterruptedException e) {


                        e.printStackTrace();
                       //sleep方法抛出这个异常之后会清除中断状态,所以需要重新设置中断状态
                     //Thread.currentThread().interrupt();
                    }
                }
            }


        };
最后的输出的结果为:
0
1
2
java.lang.InterruptedException: sleep interrupted3


	at java.lang.Thread.sleep(Native Method)
	at Concurrency.InterruptTest$1.run(InterruptTest.java:24)
	at java.lang.Thread.run(Unknown Source)
4
5
6
7
8
9
一般来说,当我们启动线程的时候,我们一般会使用某个Executor来进行启动,但是如果我们通过shutDownNow()方法来关闭线程的话,那么Executro执行的所有的任务都会被中断。如果我们只是希望中断其中的一个任务,那么我们在执行任务的时候,就应该使用submit()来执行。而不是Executor(),否则是不会成功的。
其实有些操作通过interrupte()这个方法是不能终止的。比如像IO的操作或者是Synchronized()块。下面我们来看一下这样的操作。
/*这个程序证明了IOSynchronized()块中的等待是不可以中断的。
但是其实通过程序的浏览,我们也可以知道无论是IO尝试
* 还是synchronized()中的内容,我们都不需要任何的Interrupted()的处理器。*/
//这是可以中断的阻塞的实例
class SleepBlocked implements  Runnable{
    @Override
    public void run() {
        try {
            TimeUnit.SECONDS.sleep(100);
        } catch (InterruptedException e) {
            System.out.println("InterruptedEception");
        }
        System.out.println("Exiting SleepBlocked.run()");
        
    }
}
//这是不可以中断的阻塞的实例
class IOBlocked implements  Runnable{
    private InputStream in;

    public IOBlocked(InputStream is) {
        in=is;
    }
    @Override
    public void run() {
        System.out.println("waiting for read");
        try {
            in.read();
        } catch (IOException e) {
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("Interrupted from blocked I/O");
            }else {
                throw new RuntimeException(e);
            }
        }
        System.out.println("Exiting IoBloced().run");
    }
}
//这是不可以中断的阻塞的实例
/*在这里我们为了演示synchronizedBlock,我们必须首先获取锁。
这是通过在构造器中创建了匿名的Thread()内部类来实现的。
* 这个匿名的内部类通过调用f()方法获取了对象锁。
* 由于驱动SynchronizedBlock()方法中的run()方法中的锁匿名内部类中的锁
* 是一样的。但是由于这个方法永远都不会释放锁。
* SynchronizedBlocj()中的run()方法试图在调用f().并且阻塞等待这个
* 锁被释放。*/
class SynchronizedBlocked implements Runnable{
    
    public synchronized void f() {
        while (true) {
            Thread.yield();//这里需要注意的问题是:这个yield()方法和sleep()方法都是一样的。这俩个方法在
            //阻塞的时候,都是不会释放锁的。
        }
    }
    
    public SynchronizedBlocked() {
        //在构造器中启动一个线程。
        new Thread(){
            @Override
            public void run() {
                f();
            }
        }.start();
    }
    
    @Override
    public void run() {
        System.out.println("Trying to call f()");
        f();
        System.out.println("Exiting SynchronizedBlocked.run()");
    }
}

public class Interrupting {
    private static ExecutorService exec = Executors.newCachedThreadPool();
    public static void test(Runnable r) throws InterruptedException {
        Future<?> f = exec.submit(r);
        TimeUnit.MILLISECONDS.sleep(100);
        System.out.println("Interrupting " + r.getClass().getName());
        f.cancel(true);
        System.out.println("Interrupt sent to " + r.getClass().getName());
    }

    public static void main(String[] args) throws  Exception{
        test(new SleepBlocked());
        test(new IOBlocked(System.in));
        test(new SynchronizedBlocked());
        TimeUnit.SECONDS.sleep(3);
        System.out.println("Abroting with Systm.exit(0)");
        System.exit(0);

    }
}
最后的输出的结果为:
Interrupting Concurrency.SleepBlocked
Interrupt sent to Concurrency.SleepBlocked
InterruptedEception
Exiting SleepBlocked.run()
waiting for read
Interrupting Concurrency.IOBlocked
Interrupt sent to Concurrency.IOBlocked
Trying to call f()
Interrupting Concurrency.SynchronizedBlocked
Interrupt sent to Concurrency.SynchronizedBlocked
Abroting with Systm.exit(0)
从输出的结果中,我们可以看出来,你能够中断对sleep()的调用(或者任何要求抛出InterruptedException()的调用。但是,你不能中断正在试图获取synchronized锁或者试图尝试执行IO操作的线程。这有点令人烦恼,特别是在创建执行I/O的任务的时候,因为这意味这I/O具有锁住你的多线程序的潜在的可能。特别是对基于Web的程序,这更是关乎利害。这就是说,你不能中断IO的相关的操作,这就是说IO操作有可能锁住多线程。
那么如何来避免这个问题呢?我们可以通过关闭底层资源的方法来避免这个问题的产生。
/*我们通过关闭底层资源的方法可以来解决IO操作可以锁住多线程的潜在的可能性*/
public class CloseResourse {
    public static void main(String[] args) throws  Exception{
        ExecutorService exec= Executors.newCachedThreadPool();
        ServerSocket server = new ServerSocket(8080);
        InputStream socketInput=new Socket("localhost",8080).getInputStream();
        exec.execute(new IOBlocked(socketInput));
        exec.execute(new IOBlocked(System.in));
        TimeUnit.MILLISECONDS.sleep(100);
        System.out.println("Shutting down all threads");
        exec.shutdownNow();
        TimeUnit.SECONDS.sleep(1);
        System.out.println("Closing " + socketInput.getClass().getName());
        socketInput.close();
        TimeUnit.SECONDS.sleep(1);
        System.out.println("Closing" + System.in.getClass().getName());
        System.in.close();
    }
}
最后的输出的结果为:waiting for read
waiting for read
Shutting down all threads
Closing java.net.SocketInputStream
Interrupted from blocked I/O
Exiting IoBloced().run
Closingjava.io.BufferedInputStream
在shutdownNow()被调用以后以及在俩个输入流上调用Close()之前的延迟强调的是一旦底层资源被关闭,任务就会解除阻塞了。请注意,有一点很有趣的是,interrupt()看起来发生在关闭socket而不是在关闭System.in的时刻。从输出的结果中
Interrupted from blocked I/O,我们也可以看到,这时候线程就可以被中断了。
java中的IO类和NIO类是俩个不同的内容。NIO类可以说是IO类的增强版。与普通的IO类不同的是,各种NIO类都提供了更人性化的IO中断。被阻塞的nio通道会自动地响应中断。如你所见的那样,你还可以关闭底层的资源以释放锁。注意,使用execute()来启动俩个任务的时候,并且调用e.shutdownNow()将可以很容易地终止所有的事物,而对于捕获上例中的Future,只有在将中断发给一个线程,同时不发给另外一个线程的时候,才会用得到。
下面我们在来看一下关于interrupt()的小的例子。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


class LiftOff implements Runnable {
	  protected int countDown = 5000;
	  private static int taskCount;
	  private final int id = taskCount++;
	  public LiftOff() {}
	  public LiftOff(int countDown) {
	    this.countDown = countDown;
	  }
	  public String status() {
	    return "#" + id + "(" +
	      (countDown > 0 ? countDown : "Liftoff!") + "), ";
	  }
	  public void run() {
	    while(countDown-- > 0) {
	      if(Thread.currentThread().isInterrupted()) {
	        System.out.println("Interrupted: #" + id);
	        return;
	      }
	      System.out.print(status());
	      Thread.yield();//这个并不是使线程进入阻塞状态。这是使线程进入runnable状态。失去了对cpu时间片的控制
	      
	    }
	  }
	}


	public class E20_InterruptCachedThreadPool {
	  public static void main(String[] args) throws InterruptedException {
	    ExecutorService exec = Executors.newCachedThreadPool();
	    //这里要看清楚的一点是for循环是的形式。是没有将 Thread.yield()和
	    //Thread.sleep(1000)包括进来的。
	    for(int i = 0; i < 5; i++)
	      exec.execute(new LiftOff());
	     Thread.yield();//让线程执行一段时间。
	    exec.shutdownNow();
	  }
	}
最后的输出的结果为:
Interrupted: #4
#2(4999), #0(4999), #1(4999), Interrupted: #3
Interrupted: #2
Interrupted: #0
Interrupted: #1
可是如果我们进行小小的修改,那么输出的结果就会是另外的一个样:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


class LiftOff22 implements Runnable {
	  protected int countDown = 5000;
	  private static int taskCount;
	  private final int id = taskCount++;
	  public LiftOff22() {}
	  public LiftOff22(int countDown) {
	    this.countDown = countDown;
	  }
	  public String status() {
	    return "#" + id + "(" +
	      (countDown > 0 ? countDown : "Liftoff!") + "), ";
	  }
	  public void run() {
	    while(countDown-- > 0) {
	      System.out.print(status());
	      Thread.yield();//这个并不是使线程进入阻塞状态。
	    }
	  }
	}


	public class E20 {
	  public static void main(String[] args) throws InterruptedException {
	    ExecutorService exec = Executors.newCachedThreadPool();
	    //这里要看清楚的一点是for循环是的形式。是没有将 Thread.yield()和
	    //Thread.sleep(1000)包括进来
	    for(int i = 0; i < 5; i++)
	      exec.execute(new LiftOff22());
	     Thread.yield();//让线程执行一段时间。
	    exec.shutdownNow();
	  }
	}
在这里,当我们调用shutdownNow()这个方法的时候,那么这个方法会给每一个线程都发送一个interrupt()的标志。如果线程是处于阻塞状态的话,那么这个线程就会被中断。但是如果这个线程不是处于阻塞状态的话,那么这个线程就不会被中断。这是要注意的。注意调用yield()这个方法并不是使这个线程处于阻塞的状态,只是使这个线程处于runnable状态。当这个线程再次获得cpu时间片的话,那么这个线程就会再次执行。这是要注意的。
下面我们再来看一个例子:
class NonTask {
	  static void longMethod() throws InterruptedException {
	    TimeUnit.SECONDS.sleep(60);  // Waits one minute
	  }
	}


	class Task implements Runnable {
	  public void run() {
	    try {
	      NonTask.longMethod();
	    } catch (InterruptedException ie) {
	      System.out.println(ie.toString());
	    } finally {
	      // Any cleanup code here...
	    }
	  }
	}
class Task2 implements Runnable{


	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=0;i<5;i++){
			System.out.println(i);
//			TimeUnit.MICROSECONDS.sleep(500);
		}
		
	}
	
}


	public class E18_Interruption {
	  public static void main(String[] args) throws Exception {
	    Thread t = new Thread(new Task());
	    t.start();
	    TimeUnit.SECONDS.sleep(1);
	    t.interrupt();
	   
	  }
	}
最后的输出的结果为:
java.lang.InterruptedException: sleep interrupted
说明这个线程是可以被中断的。
现在我们来看一下被互斥所阻塞的例子:
如果你尝试着在一个对象上调用其synchronized方法,而这个对象的锁已经被其它的任务获得,那么调用任务将会被挂起(阻塞),直至这个锁可以被获得为止。下面的示例中说明了,同一个互斥可以如何被同一个任务多次获得。

public class MultiBlock {
	public synchronized  void f1(int count){
		if(--count>0){
			System.out.println("f1() calling f2() with count"+count);
			f2(count);
		}
	}
	
	public synchronized void f2(int count){
		if(--count>0){
			System.out.println("f2() calling f1() with count"+count);
			f1(count);
		}
	}
	
	public static void main(String[] args){
		final MultiBlock mutiBlock=new MultiBlock();
		new Thread(){
			public void run(){
				mutiBlock.f1(10);
			}
		}.start();
	
	}
}
最后的输出的结果为:
f1() calling f2() with count9
f2() calling f1() with count8
f1() calling f2() with count7
f2() calling f1() with count6
f1() calling f2() with count5
f2() calling f1() with count4
f1() calling f2() with count3
f2() calling f1() with count2
f1() calling f2() with count1
在main中创建了一个调用f1()的Thread.然后f1()和f2()互相调用直至count变为0.由于这个任务在第一次对f1()的调用中已经获得了multiBlock对象锁。因此在同一个任务中将在对f2()的调用中再次获得这个锁。以此类推。这么做是有意义的。因为一个任务应该能够调用在同一个对象中的其它的synchronized方法。而这个任务已经持有锁了。
在这里我想再强调的一点就是,当你在线程上调用interrupt()的时候,中断发生的唯一时刻是在任务要进入到阻塞操作中,或者已经在阻塞操作内部时(如你所见,除了不可中断的IO或者被阻塞的synchronized方法外,在其余的情况下,你无所事事)。因此如果你调用interrupt()以停止某个任务的时候,如果在run()循环中碰巧没有产生阻塞的情况下调用,那么我们就可以通过检查中断的方法来进行退出。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱coding的同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值