刚刚wm问我了一道线程的问题,由于自己一直是coder界里的渣渣,所以就需要恶补一下。
2016年4月2号题目如下。
import java.util.logging.Handler;
/**
* 完SyncTask的start方法,要求
* 1,SyncTask的派生类的run方法抛到Handler所属的线程执行。
* 2,SyncTask派生类的执行线程等待返回,除非等待的超时timeout
* 3,如果timeout或出错,则返回默认值defultRet
*/
public class wm {
public abstract class SyncTask {
protected abstract R run();
private R result;
private byte[] lock = new byte[0];
private boolean notified = false;
private Runnable task = new Runnable() {
@Override
public void run() {
R ret = SyncTask.this.run();
synchronized (lock) {
result = ret;
lock.notify();
notified = true;
}
}
};
/***
* 将任务抛到其他线程,同步等待其返回结果
* @param timeout 超过指定时间则直接返回ms
* @param defaultRet 默认返回值,即超时后或出错的返回值
* @param handler 执行线程handler
* @return
*/
public R start(final long timeout, final R defaultRet, Handler handler) {
}
}
}
见,知乎 https://www.zhihu.com/question/43416744
1,基础知识
线程的等待与唤醒
/**
* Created by xk on 2016/4/2.
*/
public class WaitTest {
public static void main(String[] args) {
ThreadA t1 = new ThreadA("t1");
synchronized (t1) {
try {
//启动线程
System.out.println(Thread.currentThread().getName() + " start t1");
t1.start();
System.out.println(Thread.currentThread().getName() + "wait()");
t1.wait();
System.out.println(Thread.currentThread().getName() + "continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class ThreadA extends Thread {
public ThreadA(String name) {
super(name);
}
public void run() {
synchronized (this) {
System.out.println(Thread.currentThread().getName() + "call notify()");
notify();
}
}
}
输出
Object类中关于等待/唤醒的API详细信息如下:
notify() -- 唤醒在此对象监视器上等待的单个线程。
notifyAll() -- 唤醒在此对象监视器上等待的所有线程。
wait() -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout) -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout, int nanos) -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。
1. 在上述程序中,主线程是main,t1是main线程中启动的线程,而锁是t1对象的同步锁。
2. 主线程调用new 新建一个线程,通过synchronized(t1)来获取t1对象的同步锁,然后调用t1.start()来启动线程t1.
3. 主线程,执行wait释放t1的锁,进入等待(阻塞)状态,等待t1对象上的线程通过notify或者notifyAll将其唤醒。
4. 线程t1运行之后,通过synchronized(this)获取当前对象的锁,调用,notify唤醒当前对象上的等待的线程,即main。
5. 线程t1运行完毕,释放当前对象的锁,紧接着,主线程获取t1对象的锁,接着运行。
补充,
1,t1.wait()是让“主线程main”等待,而不是t1.当前线程调用wait的时候,必须拥有该对象的同步锁,调用之后,释放该锁,直到等待的调用对象的同步锁的notify或者notifyAll方法,该线程就会获得该对象的同步锁,继续运行。
wait()的作用是让“当前线程”等待,而“当前线程”是指正在cpu上运行的线程!
这也意味着,虽然t1.wait()是通过“线程t1”调用的wait()方法,但是调用t1.wait()的地方是在“主线程main”中。而主线程必须是“当前线程”,也就是运行状态,才可以执行t1.wait()。所以,此时的“当前线程”是“主线程main”!因此,t1.wait()是让“主线程”等待,而不是“线程t1”!
wait(long timeout)会让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout)会让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
下面的示例就是演示wait(long timeout)在超时情况下,线程被唤醒的情况。
public ThreadA(String name) {
super(name);
}
public void run() {
System.out.println(Thread.currentThread().getName() + " run ");
// 死循环,不断运行。
while(true)
}
}
public class WaitTimeoutTest {
public static void main(String[] args) {
ThreadA t1 = new ThreadA("t1");
synchronized(t1) {
try {
// 启动“线程t1”
System.out.println(Thread.currentThread().getName() + " start t1");
t1.start();
// 主线程等待t1通过notify()唤醒 或 notifyAll()唤醒,或超过3000ms延时;然后才被唤醒。
System.out.println(Thread.currentThread().getName() + " call wait ");
t1.wait(3000);
System.out.println(Thread.currentThread().getName() + " continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
结果说明:
如下图,说明了“主线程”和“线程t1”的流程。
(01) 注意,图中"主线程" 代表WaitTimeoutTest主线程(即,线程main)。"线程t1" 代表WaitTest中启动的线程t1。 而“锁” 代表“t1这个对象的同步锁”。
(02) 主线程main执行t1.start()启动“线程t1”。
(03) 主线程main执行t1.wait(3000),此时,主线程进入“阻塞状态”。需要“用于t1对象锁的线程通过notify() 或者 notifyAll()将其唤醒” 或者 “超时3000ms之后”,主线程main才进入到“就绪状态”,然后才可以运行。
(04) “线程t1”运行之后,进入了死循环,一直不断的运行。
(05) 超时3000ms之后,主线程main会进入到“就绪状态”,然后接着进入“运行状态”。
4. wait() 和 notifyAll()
通过前面的示例,我们知道 notify() 可以唤醒在此对象监视器上等待的单个线程。
下面,我们通过示例演示notifyAll()的用法;它的作用是唤醒在此对象监视器上等待的所有线程。
private static Object obj = new Object();
public static void main(String[] args) {
ThreadA t1 = new ThreadA("t1");
ThreadA t2 = new ThreadA("t2");
ThreadA t3 = new ThreadA("t3");
t1.start();
t2.start();
t3.start();
try {
System.out.println(Thread.currentThread().getName()+" sleep(3000)");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(obj) {
// 主线程等待唤醒。
System.out.println(Thread.currentThread().getName()+" notifyAll()");
obj.notifyAll();
}
}
static class ThreadA extends Thread{
public ThreadA(String name){
super(name);
}
public void run() {
synchronized (obj) {
try {
// 打印输出结果
System.out.println(Thread.currentThread().getName() + " wait");
// 唤醒当前的wait线程
obj.wait();
// 打印输出结果
System.out.println(Thread.currentThread().getName() + " continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
运行结果:
结果说明:
参考下面的流程图。
(01) 主线程中新建并且启动了3个线程"t1", "t2"和"t3"。
(02) 主线程通过sleep(3000)休眠3秒。在主线程休眠3秒的过程中,我们假设"t1", "t2"和"t3"这3个线程都运行了。以"t1"为例,当它运行的时候,它会执行obj.wait()等待其它线程通过notify()或额nofityAll()来唤醒它;相同的道理,"t2"和"t3"也会等待其它线程通过nofity()或nofityAll()来唤醒它们。
(03) 主线程休眠3秒之后,接着运行。执行 obj.notifyAll() 唤醒obj上的等待线程,即唤醒"t1", "t2"和"t3"这3个线程。 紧接着,主线程的synchronized(obj)运行完毕之后,主线程释放“obj锁”。这样,"t1", "t2"和"t3"就可以获取“obj锁”而继续运行了!
5. 为什么notify(), wait()等函数定义在Object中,而不是Thread中
Object中的wait(), notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作。
wait()会使“当前线程”等待,因为线程进入等待状态,所以线程应该释放它锁持有的“同步锁”,否则其它线程获取不到该“同步锁”而无法运行!
OK,线程调用wait()之后,会释放它锁持有的“同步锁”;而且,根据前面的介绍,我们知道:等待线程可以被notify()或notifyAll()唤醒。现在,请思考一个问题:notify()是依据什么唤醒等待线程的?或者说,wait()等待线程和notify()之间是通过什么关联起来的?答案是:依据“对象的同步锁”。
负责唤醒等待线程的那个线程(我们称为“唤醒线程”),它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。虽然,等待线程被唤醒;但是,它不能立刻执行,因为唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放了“对象的同步锁”之后,等待线程才能获取到“对象的同步锁”进而继续运行。
总之,notify(), wait()依赖于“同步锁”,而“同步锁”是对象锁持有,并且每个对象有且仅有一个!这就是为什么notify(), wait()等函数定义在Object类,而不是Thread类中的原因。