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类中的原因。







posted @ 2017-08-07 14:52  clnchanpin  阅读(269)  评论(0)    收藏  举报