线程sleep与wait的区别(拓展yield和join)
关于这道题(多选题)关于 sleep()和 wait(),以下描述正确的一项是( )
A.sleep 是线程类(Thread)的方法,wait 是 Object 类的方法;
B.sleep 不释放对象锁,wait 放弃对象锁;
C.sleep 暂停线程、但监控状态仍然保持,结束后会自动恢复;
D.wait 后进入等待锁定池,只有针对此对象发出 notify 方法后获得 对象锁进入运行状态
解析:我们先整理下sleep和wait的区别
sleep
sleep是Thread提供的静态方法,作用是暂停此线程,这时候呢其他线程就可以进行,也就是在整个线程队列中重新排队,直到睡眠结束后等待CPU的调度(注意,这时候并不代表它具有高优先级,一样要同其他线程竞争,所以并不一定会马上执行)。还有一个最大的区别是它并没有放弃对象锁,仍然保持监控状态,结束后呢就会自动恢复继续往下执行。
wait
wait是Object提供的方法,执行时,当前线程将被放入等待池(注意不是锁池),放开对象锁,其他的线程就可以争夺这个锁,这时需要等待notify或者notifyAll或者指定的睡眠时间唤醒线程池中的线程,被唤醒后就去争夺锁(仍然在等待池,只不过是有了争夺锁的权限),抢到后继续往下执行。
回到我们的题目中,ABC不用多说,关于D,这里要注意题说的“只有”,我们知道是notify和notifAll或者指定的睡眠时间到了就可以获得,所以这里D是错的。
这里拓展下yield和join方法的作用与区别
yield
作用跟sleep类似,放弃当前CPU权限暂停当前线程,不会放弃锁对象。区别在于暂停当前进程,是为了让更高优先级或者同优先级的进程先执行,那什么时候会恢复由线程调度器说了算,比如执行yield时没有其他更高优先级的线程要执行时,那么该线程就会恢复。
join
join的作用是让加入的线程先执行,被插入的线程等待它执行完才能执行,可以理解为插队。例如如下代码,按照理解是t1会先执行完后t2才能执行
public static void main(String[] args) {
...//省略创建线程对象步骤
t1.start();
t1.join(); //调用wait方法阻塞当前main方法,等待t1执行完
t2.start();
}
join释放对象锁问题
有一个问题,如果t1、t2两个进程抢的是一个锁对象,那么被(插队)阻塞的线程会不会放开锁呢。我们来看看join方法的源码
if (millis == 0) {
while (isAlive()) {
wait(0);
}
可以看到实际上join方法调用的是wait方法,那么我们知道wait方法是会放弃对象锁的,而join方法是当前线程放弃是调用者t1这个对象锁(谁调用就是谁xx.join就是放弃的xx对象锁),而不是线程中的抢的锁对象。看下面例子:
package com.wangshili;
public class TestJoinMethod {
static Integer number = 1000;
public static void main(String[] args) {
Thread t1 = new Thread() { //创建t1线程
@Override
public void run() {
while(true) {
synchronized (number) { //这里抢的是number锁
if(number <= 0)break;
number = number -1;
System.out.println(getName()+":"+number);
}
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
while(true) {
synchronized (number) {
try {
System.out.println(getName()+"抢到锁"); //t2抢到锁后t1插队
t1.join();//t1插队,t2线程将会放弃t1线程锁,不是number
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(number <= 0)break;
number = number -1;
System.out.println(getName()+":"+number);
}
}
}
};
t1.start();//启动线程
t2.start();
}
}
我们看下执行结果,会发现每当t2抢到锁,t1插入,这个线程就会卡死,这就是因为它们抢的锁是number这个对象,而join方法让当前线程放弃的对象锁是t1,所以就会在t1线程的while中卡死,不断的抢锁,而t2是拿着锁的。
我们修改下代码就可以知道,让两个线程抢t1的锁
package com.wangshili;
public class TestJoinMethod {
static Integer number = 1000;
static Thread t1 =null;//提前定义t1变量
public static void main(String[] args) {
t1 = new Thread() {
@Override
public void run() {
while(true) {
synchronized (t1) { //换成抢t1的锁
if(number <= 0)break;
number = number -1;
System.out.println(getName()+":"+number);
}
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
while(true) {
synchronized (t1) {//换成抢t1的锁
try {
System.out.println(getName()+"抢到锁");
t1.join();//t1插队
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(number <= 0)break;
number = number -1;
System.out.println(getName()+":"+number);
}
}
}
};
t1.start();//启动线程
t2.start();
}
}
从运行结果来看,很明显,当抢的锁和join方法对象一致的时候,就不会出现卡死情况,说明t2已经放弃了锁,t1拿到锁执行语句。
总结
几种方法用的场合不一样,了解最大的区别就可以了。像wait和sleep一个不放弃对象锁,一个是放弃对象锁,wait是放弃的对象锁是引用它的对象,不要与线程中的加锁对象搞混。yield与sleep类似但不可以指定休眠时间,所以用的相对较少,join最大的区别就是插队了。