我们先放一张图:
相信看完这篇文章之后你能很容易看懂这张图
1,线程睡眠sleep():让当前线程进入阻塞状态,不会释放锁
属于Thread类的静态方法,需要使用try-catch捕获异常
static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程睡眠(暂停执行)。 static void sleep(long millis, int nanos) 指定的毫秒数加指定的纳秒数内让当前正在执行的线程睡眠(暂停执行)。
1,线程睡眠的意思是暂停执行,交出CPU,让CPU去执行其他的任务。
2,使当前线程(即调用该方法的线程)暂停执行一段时间,CPU可以去执行其他线程,但是由于不会释放锁,所以其他线程还是不能访问此sync方法或者sync代码块
3,例如有两个线程同时执行(没有synchronized)一个线程优先级为MAX_PRIORITY【10】,另一个为MIN_PRIORITY【1】,如果没有Sleep()方法,只有高优先级的线程执行完毕后,低优先级的线程才能够执行;但是高优先级的线程sleep(500)后,低优先级就有机会执行了。
总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。
2,wait():让当前线程进入阻塞状态,释放锁
属于Object()类final方法,必须在 synchronized函数或synchronized-block中进行调用。否则编译通过,运行报IllegalMonitorStateException
void wait() :在其他线程调用此对象的 notify() 方法或者 notifyAll()方法前,导致当前线程等待。 void wait(long timeout):在其他线程调用此对象的notify() 方法 或者 notifyAll()方法,或者超过指定的时间量前,导致当前线程等待。
1,调用wait()方法的时候,线程会放弃对象锁,其他线程可以进入sync数据块中,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
2,wait()方法作用在Object对象上.
3,notify()/notifyall():在同步锁中,让当前线程从阻塞状态变为就绪状态,与wait()配套
属于Object类final方法,必须在 synchronized函数或synchronized-block中进行调用。
notify():唤醒当前对象监视器上的一个等待线程,当调用notify()方法后,将从对象的等待池中移走一个任意(由操作系统决定)的线程并放到锁标志等待池中,只有锁标志等待池中线程能够获取锁标志;然后等待获取锁,回到wait()前的中断现场 notifyAll():唤醒当前对象监视器上的所有等待线程,从对象等待池中移走所有等待那个对象的线程并放到锁标志等待池中。
4,线程中断interrupt():中断线程并收到一个InterruptedException异常
void interrupt() 中断线程。 static boolean interrupted() 方法是一个静态方法,它是判断当前线程的中断状态 需要注意的是,线程的中断状态会由该方法清除。 换句话说,如果连续两次调用该方法,则第二次调用将返回 false boolean isInterrupted() 测试线程是否已经中断。
调用interrupt中断正在处于阻塞状态的线程,使其变为就绪状态,可以在异常捕获方法里做停止线程阻塞的逻辑操作。
他的原理就是:
interrupt()会立即将线程的中断标记设为“true” 但是由于线程处于阻塞状态,所以该“中断标记”会立即被清除为“false” 同时,会产生一个InterruptedException的异常。
另外
本线程中断自己是被允许的;其它线程调用本线程的interrupt()方法时, 会通过checkAccess()检查权限。这有可能抛出SecurityException异常。 如果线程被阻塞在一个Selector选择器中,那么通过interrupt()中断它时 线程的中断标记会被设置为true,并且它会立即从选择操作中返回。 如果不属于前面所说的情况,那么通过interrupt()中断线程时 它的中断标记会被设置为“true”。 中断一个“已终止的线程”不会产生任何操作。
5,线程让步yield():让线程进入就绪状态,不会释放锁
static void yield() 暂停当前正在执行的线程对象,让当前线程交出CPU权限,让CPU去执行其他的线程,不会释放锁
1,该方法与sleep()类似,只是不能由用户指定暂停多长时间,并且yield()方法只能让同优先级的线程有执行的机会。
2,yield方法会临时暂停当前正在执行的线程,来让有同样优先级的正在等待的线程有机会执行。如果没有正在等待的线程,或者所有正在等待的线程的优先级都比较低,那么该线程会继续运行。
3,执行了yield方法的线程什么时候会继续运行由线程调度器来决定,不同的厂商可能有不同的行为。yield方法不保证当前的线程会暂停或者停止,但是可以保证当前线程在调用yield方法时会放弃CPU。
6,线程合并join():进入阻塞状态,释放锁,交出CPU执行权限
使当前线程停下来等待,直至另一个调用join方法的线程终止。值得注意的是,线程的在被激活后不一定马上就运行,而是进入到可运行线程的队列中。但是join()可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException。
join()方法优先执行调用该方法的线程,再执行当前线程。 也就是等待该方法的调用线程执行完毕后才往下继续执行。 注意该方法也需要捕捉异常。 看join()方法源码可知:实际上调用join方法就是调用了Object的wait方法。
在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。
对于以下代码,虽然 b 线程先启动,但是因为在 b 线程中调用了 a 线程的 join() 方法,b 线程会等待 a 线程结束才继续执行,因此最后能够保证 a 线程的输出先于 b 线程的输出。
public class JoinExample { private class A extends Thread { @Override public void run() { System.out.println("A"); } } private class B extends Thread { private A a; B(A a) { this.a = a; } @Override public void run() { try { a.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("B"); } } public void test() { A a = new A(); B b = new B(a); b.start(); a.start(); } }
public static void main(String[] args) { JoinExample example = new JoinExample(); example.test(); } 输出: A,B
另外,终止线程的方式:
1,终止处于"阻塞状态"的线程
通常,我们通过“中断”方式终止处于“阻塞状态”的线程。
当线程由于被调用了sleep(), wait(), join()等方法而进入阻塞状态;若此时调用线程的interrupt()将线程的中断标记设为true。由于处于阻塞状态,中断标记会被清除,同时产生一个InterruptedException异常。将InterruptedException放在适当的为止就能终止线程2,终止处于"运行状态"的线程
(01) 通过“中断标记”终止线程。
形式如下:@Override public void run() { while (!isInterrupted()) { // 执行任务... } }
说明:isInterrupted()是判断线程的中断标记是不是为true。当线程处于运行状态,并且我们需要终止它时;可以调用线程的interrupt()方法,使用线程的中断标记为true,即isInterrupted()会返回true。此时,就会退出while循环。
注意:interrupt()并不会终止处于“运行状态”的线程!它会将线程的中断标记设为true。(02) 通过“额外添加标记”。
形式如下:private volatile boolean flag= true; protected void stopTask() { flag = false; } @Override public void run() { while (flag) { // 执行任务... } }
说明:线程中有一个flag标记,它的默认值是true;并且我们提供stopTask()来设置flag标记。当我们需要终止该线程时,调用该线程的stopTask()方法就可以让线程退出while循环。
注意:将flag定义为volatile类型,是为了保证flag的可见性。即其它线程通过stopTask()修改了flag之后,本线程能看到修改后的flag的值。
综合线程处于“阻塞状态”和“运行状态”的终止方式,比较通用的终止线程的形式如下:
@Override public void run() { try { // 1. isInterrupted()保证,只要中断标记为true就终止线程。 while (!isInterrupted()) { // 执行任务... } } catch (InterruptedException ie) { // 2. InterruptedException异常保证,当InterruptedException异常产生时,线程被终止。 } }
总结:
1、wait()和sleep()的区别:
wait()方法定义在Object类下,必须用在synchronized代码中,否则会报IllegalMonitorStateException异常,而 sleep()方法定义在Thread类下,没什么限制条件;
wait()方法作用在Object对象上, 而sleep()方法作用在Thread对象上;
wait()方法释放同步锁,sleep()方法不释放锁;
wait()需要定义在循环里(一般是while循环),防止线程唤醒后,唤醒条件再一次不满足,而sleep()尽量不要放在循环中调用。
2、wait()和notify()、notifyAll():
这三个方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块内使用。synchronized关键字用于保护共享数据,阻止其他线程对共享数据的存取,但是这样程序的流程就很不灵活了,如何才能在当前线程还没退出synchronized数据块时让其他线程也有机会访问共享数据呢?此时就用这三个方法来灵活控制。
注意 这三个方法都是java.lang.Object的方法。
3、wait和yield(或sleep)的区别:
wait()是让线程由“运行状态”进入到“阻塞状态”,而yield()是让线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权。
wait()是会线程释放它所持有对象的同步锁,而yield()方法不会释放锁。
线程方法名称 是否释放同步锁 是否需要在同步的代码块中调用 方法是否已废弃 是否可以被中断 sleep() 否 否 否 是 wait() 是 是 否 是 join() 是 是 否 是