java线程同步问题——由腾讯笔试题引发的风波
刚刚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<R> {
        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)在超时情况下,线程被唤醒的情况。
// WaitTimeoutTest.java的源代码
class ThreadA extends Thread{
    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();
            }
        }
    }
}
执行结果:
main start t1
main call wait
t1 run // 大约3秒之后...输出“main continue”
main continue
结果说明:
例如以下图。说明了“主线程”和“线程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()的使用方法;它的作用是唤醒在此对象监视器上等待的全部线程。
public class NotifyAllTest {
    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();
                }
            }
        }
    }
}
执行结果:
t1 wait
main sleep(3000)
t3 wait
t2 wait
main notifyAll()
t2 continue
t3 continue
t1 continue
结果说明:
參考以下的流程图。
 
(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类中的原因。
 
                     
                    
                 
                    
                 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号