Java中Wait,Sleep和Yield方法的区别

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java中Wait,Sleep和Yield方法的区别相关的知识,希望对你有一定的参考价值。

参考技术A 共同点:
1. 他们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。
2. wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException。
如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep/join,则线程B会立刻抛出InterruptedException,在catch() 中直接return即可安全地结束线程。
需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出InterruptedException 。
不同点:
1. Thread类的方法:sleep(),yield()等
Object的方法:wait()和notify()等
2. 每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。
sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
3. wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
4. sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常

Java多线程之sleep,wait,join和yield关键字,以及线程的关闭

在java或者android中,使用Thread和Runnable就可以玩多线程了,这个成本比较低,也没什么好说的,今天主要是针对多线程中主要的关键字wait,sleep,join和yield做个笔记,加强一下印象。

wait

wait方法一般都是和notity()或者notifyAll()成对出现的。当某个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去了对象的锁功能,使得其他线程可以访问该对象。用户可以使用notify或者notifyAll或者指定睡眠时间来唤醒当前等待池中的线程。wait,notify和notifyAll方法都必须放在synchronized 代码块中,否则就会报java.lang.IllegalMonitorStateException。
  下面的例子中,在主线程中会存在一个waitObject对象使用wait方法进行等待,而开启子线程睡眠3秒之后,notifyAll该waitObject对象,使得主线程继续执行:

private static Object waitObject = new Object();
	
	public static void main(String[] args) 
		System.out.println("主线程开始运行");
		WaitThread thread = new WaitThread();
		thread.start();
		
		long t1 = System.currentTimeMillis();
		try
			synchronized(waitObject) 
				System.out.println("主线程等待");
				waitObject.wait();
				System.out.println("主线程等待结束");
			
		catch(Exception e)
			e.printStackTrace();
		
		
		long t2 = System.currentTimeMillis();
		
		System.out.println("最终时间为:" + (t2 - t1));
		
	
	
	//定义等待线程
	static class WaitThread extends Thread
		@Override
		public void run() 
			System.out.println("进入子线程run方法");
			try
				sleep(3000);
				synchronized(waitObject) 
					waitObject.notifyAll();
					System.out.println("子线程notifyAll结束");
				
			catch(Exception e) 
				e.printStackTrace();
			
		
	

程序运行的结果为:

可见wait和notity可用于等待机制的实现,当条件不满足时进行等待,一旦条件满足,则notity或者notifyAll唤醒等待线程继续执行。经典的消费者-生产者模式可以使用wait和notity进行设计。

join

等待目标线程执行完成之后再继续执行。说的比较含糊,还是来看看例子吧。下面有两个工作子线程,都需要进行2s的耗时才能完成任务:

public static void main(String[] args) throws Exception 
		Thread t1 = new WorkThread("work1");
		Thread t2 = new WorkThread("work2");
		
		t1.start();
		//t1.join();
		
		t2.start();
		//t2.join();
		
		System.out.println("当前主线程结束");
	
	
	//工作线程
	static class WorkThread extends Thread
		
		public WorkThread(String name)
			super(name);
		
		
		@Override
		public void run() 
				try 
					sleep(2000);
				 catch (InterruptedException e) 
					e.printStackTrace();
				
				System.out.println("the current thread is " + getName());
			
	

首先先注释掉join方法,直接运行,可以看到的结果如下图:

我们可以看到,主线程首先执行完成,而后两个子线程分别执行完成,现在我们打开我们的t1.join()和t2.join()方法,得出的结果为:

程序执行的顺序是 t1->t2->main thread,相当于三个程序的串联执行,这也就是join的作用,一旦某个线程使用了join,那么它就先执行完毕,其他线程才有机会继续执行。

yield

线程礼让。使用yield方法的线程将由运行状态转变为就绪状态,也就是让出执行的状态,让其他线程得以优先执行,但是其他线程未必一定是有限执行的。简单通俗一点,就是线程先等着,我会让其他线程有优先的机会去运行。下面的例子可以简单说明问题:有两个单独的子线程t1,t2分别独自运行,t1中run遍历到4时,会执行yield方法,那么我们的猜测就是此时t1将会变回就绪状态,不再抢夺CPU资源,等到其他线程执行完毕后,再次从就绪状态变为运行状态,从而完成以后的任务。代码如下:

public static void main(String[] args) 
		//线程t1运行到4时 会执行yield方法
		Thread t1 = new YieldClass("线程1",4);
		//线程t2将一直运行下去
		Thread t2 = new YieldClass("线程2",-1);
		
		t1.start();
		t2.start();
		
		System.out.println("主线程结束");
	
	
	static class YieldClass extends Thread
		
		public int mIndex ;
		
		public YieldClass(String name ,int index) 
			super(name);
			this.mIndex = index;
		
		
		@Override
		public void run() 
			for(int i = 0 ; i < 10 ; i++) 
				System.out.println(Thread.currentThread().getName() + "---" + i);
				if(i == mIndex) 
					yield();
				
			
				
	
	

我们来看运行的效果图:

可以看到图中,t1和t2先并行运行,可是当t1运行到4时,由于执行了yield方法,此时t1将会变为就绪状态,t2线程会执行下去,t2线程执行完成之后,t1才会继续执行,跟我们预想的方式是一样的。

sleep

sleep方法是我们平常用得最多的,它是Thread的静态函数,作用是使得调用的Thread进入休眠状态。由于是Static修饰的方法,因此不能修改对象的锁机制,所以当一个synchronized块中调用了sleep方法,线程虽然休眠了,但是对象的锁机制并没有被释放。其他线程将会无法访问到这个对象。下面举个例子:有两个子线程,一个需要睡眠3s,另一个不需要睡眠,两个子线程都使用了synchronized块,我们来看看两个线程结束之后所用的时间,代码如下:

public class TestSleep 

	private static Object mLock = new Object();
	
	public static void main(String[] args) 
		Thread t1 = new SleepThread();
		Thread t2 = new WordThread() ;
		
		t1.start();
		
		t2.start();
		
		System.out.println("-----主线程执行完成-----"+ System.currentTimeMillis());
	
	
	static class SleepThread extends Thread 
		@Override
		public void run() 
			synchronized(mLock)
				try 
					sleep(3000);
				 catch (InterruptedException e) 
					e.printStackTrace();
				
				
				System.out.println("----睡眠线程执行完成-----" + System.currentTimeMillis());
			
		
	
	
	static class WordThread extends Thread
		
		@Override
		public void run() 
			synchronized (mLock) 
				System.out.println("----工作线程执行完成-----" + System.currentTimeMillis());
			
		
	


结果为:

我们发现了睡眠线程和工作线程几乎都是等待了3秒之后才结束的,这就表明了sleep引用了对象锁,其他线程将无法访问该对象了。我们去掉synchronized代码块,再来看一次结果:

我们发现睡眠线程和主线程几乎同时完成工作,只有睡眠线程睡眠3秒之后才结束工作,由于没有引用相同的对象,线程之间不影响各自的工作,此时就不存在同步的问题了。

线程的关闭

线程Thread提供了一个stop()方法,但是在1.2已经被注释为过时了。如下面例子:

        Thread thread = new Thread(() -> 
            for (int i = 0; i < 2_000_000; i++) 
                System.out.println("i:" + i);
            
        );

        thread.start();

        try 
            Thread.sleep(40);
         catch (InterruptedException e) 
            e.printStackTrace();
        

        thread.stop();
    

使用stop()方法可以瞬间结束线程,但是在1.2之后就已经被认为过时了,主要是因为安全问题,使用Thread.stop()方法会直接释放所有的monitors(监视器,wait和join方法中所理解的锁,就是可以理解为monitors)。一旦这些monitors被完全释放,多线程中需要使用到monitors都会出现问题。那么该如何正确的关闭线程呢?
其实Thread.提供了

thread.interrupt();

interrupt方法只是给当前线程设置了一个打断的标致,而需要用

Thread.interrupted()

来判断当前线程是否被打断。
那么正确的线程结束方法应该为:

        Thread thread = new Thread()
            @Override
            public void run() 
                for (int i = 0; i < 2_000_000; i++) 
                    if(interrupted())
                        //这里判断线程是否被打断 进而写自己的逻辑结束线程
                        break;
                    
                    System.out.println("i:" + i);
                
            
        ;

        thread.start();

        try 
            Thread.sleep(400);
         catch (InterruptedException e) 
            e.printStackTrace();
        
        
        //thread.stop();
        
        thread.interrupt();

而对于InterruptedException,可以查看一下源码参考,它只会发生在线程的等待时,睡眠时或者被占用时才会被抛出的异常,本身没有太多的意义,只是一个线程的应该被打断,但此时它却正在做其他的事情。

好了,今天基本上就说到这里了,由于以前很少了解这些东西,以致很多关于多线程方面的东西都看得不是很懂,今天算是个入门吧。

以上是关于Java中Wait,Sleep和Yield方法的区别的主要内容,如果未能解决你的问题,请参考以下文章

Java中sleep,wait,yield,join的区别

sleep和yield的区别

java线程中的 sleep() wait() yield()

Java并发之wait notify yield sleep join

Java中线程的yield(),sleep()以及wait()的区别

Java多线程之sleep,wait,join和yield关键字,以及线程的关闭