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()
Java并发之wait notify yield sleep join