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







  • 相关阅读:
    工欲善其事,必先利其器
    年度总结
    人脸解锁从底层到上层(一)
    Hexo NexT 主题添加评论和文章阅读量
    摄影历程-第一章
    西藏之旅
    软件测试和评估
    WordCount优化
    WordCount编码与测试
    值得深入思考的五个问题
  • 原文地址:https://www.cnblogs.com/clnchanpin/p/7299119.html
Copyright © 2020-2023  润新知