• 线程中的join方法,与synchronized和wait()和notify()的关系


            什么时候要用join()方法?

            1,join方法是Thread类中的方法,主线程执行完start()方法,线程就进入就绪状态,虚拟机最终会执行run方法进入运行状态.此时.主线程跳出start方法往下执行

            2,两个线程以上,当一个线程需要另一个线程执行的结果时,可以在该线程之前调用另一个线程对象的join方法,如下:

    public class TestThread {
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new ThreadA("ThreadA");
            Thread t2 = new Thread(new ThreadB(),"ThreadB");
            t1.start();//主线程调用start方法,开启t1线程,
            t1.join();//主线程调用join方法,获取对象t1对象锁,挂起主线程.直到t1线程结束
            t2.start();//主线程调用start方法,开启t2线程
            t2.join();//主线程调用join,获取t2对象锁,挂起主线程,直到t2线程结束
            System.out.println(Thread.currentThread().getName()+"主线程结束");
        }
    }
    如上代码,主线程一路下来调用其他线程的对象的join方法,就会被挂起直到该线程对象所在的线程结束.从而实现线程的顺序执行.

           

            查看join的源码,该方法是同步方法,锁是t1线程所在对象的对象锁,我们可以看到,wait总是在synchronized代码块里面使用,并且在while循环中,有wait()就必然有notify()/notifyAll(),所以同样的一般情况下notify()/notifyAll()也是在synchronized代码块中的while使用的,synchronized,while,wait,notify结合可以实现经典的生产者消费者模式.

    public final synchronized void join(long millis)
        throws InterruptedException {
            long base = System.currentTimeMillis();
            ....
            //主线程调用join方法就拥有了t1对象的锁,wait(0)一直处于等待状态,
            //直到某个线程调用了该对象的notify或者notifyAll()
            if (millis == 0) {
                while (isAlive()) {
                    wait(0);
                }
            } else {
               ....
            }
        }
            
    在join方法中我们看到了wait(),那么一定在某个地方有notify方法,该方法则在jvm源码中,这个段源码的作用是结束线程,在线程退出前需要做一些动作,其中就有调用notify_all的动作,看注释,还将isAlive变为false,
    // 位于/hotspot/src/share/vm/runtime/thread.cpp中
    void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
        // ...
        // Notify waiters on thread object. This has to be done after exit() is called
        // on the thread (if the thread is the last thread in a daemon ThreadGroup the
        // group should have the destroyed bit set before waiters are notified).
        // 有一个贼不起眼的一行代码,就是这行
        ensure_join(this);
        // ...
    }
    static void ensure_join(JavaThread* thread) {
        // We do not need to grap the Threads_lock, since we are operating on ourself.
        Handle threadObj(thread, thread->threadObj());
        assert(threadObj.not_null(), "java thread object must exist");
        ObjectLocker lock(threadObj, thread);
        // Ignore pending exception (ThreadDeath), since we are exiting anyway
        thread->clear_pending_exception();
        // Thread is exiting. So set thread_status field in  java.lang.Thread class to TERMINATED.
        java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
        // Clear the native thread instance - this makes isAlive return false and allows the join()
        // to complete once we've done the notify_all below
        java_lang_Thread::set_thread(threadObj(), NULL);
        // 唤醒其他线程
        lock.notify_all(thread);
        // Ignore pending exception (ThreadDeath), since we are exiting anyway
        thread->clear_pending_exception();
    }
     

      由此我们即看到了notify,也看到了isAlive变为false,所以主线程再挂起的时候,会等待该对象线程t1结束并调用notify,唤醒之后则跳出循环检测isAlive为false则跳

    出循环结束join方法,这就实现了线程的顺序执行,

    码解读完毕

            从代码中的while(isAlive){wait()}说起,

          如果主线程在等待状态,被其他线程唤醒呢,所以前文我说,notify也一般配合synchronized,while使用,这个一般情况下是消费者生产者模式,但是在jvm源码中并没

    有这么用,这是要等待t1线程结束前调用,所以这个isAlive就是保证一定被t1线程结束唤醒,而其他线程唤醒while判断之后再次进入挂起状态.

    几个问题的辨析, 

            1,线程之间的竞争是同一个对象锁的竞争或者同一个类锁的竞争,与其他对象的锁无关.其他线程抢别的对象的锁,执行别对象的wait/notify方法都与竞争本对象

    锁的线程无关.

           2, 锁分为对象锁,类锁,锁的使用者叫线程,锁拦截的对象也是线程,不是什么方法,或者其他,.

            对象锁,表示对象里面所有被synchronized(this)修饰的代码块和synchronized 修饰的普通方法块,不能同时被两个线程执行.详情见:synchronized的使用方法和作用域:

    https://mp.weixin.qq.com/s?__biz=MzI4NTEzMjc5Mw==&mid=2650554746&idx=1&sn=8e45e741ca9f058dba1f3ebbea0e9f07&chksm=f3f833ecc48fbafa295e355c1cdd52dc4259f161dafdc1703d181a5e9f4f76563c98493bd221&token=2005887224&lang=zh_CN#rd

            类锁是被synchronized(MyClass.class)和static synchronized 修饰的静态方法,不能同时被两个线程执行,详情见:synchronized的使用方法和作用域        

          https://mp.weixin.qq.com/s?__biz=MzI4NTEzMjc5Mw==&mid=2650554746&idx=1&sn=8e45e741ca9f058dba1f3ebbea0e9f07&chksm=f3f833ecc48fbafa295e355c1cdd52dc4259f161dafdc1703d181a5e9f4f76563c98493bd221&token=2005887224&lang=zh_CN#rd

            3,线程释放锁的场景,

            a,执行完同步代码块,

            b,在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放

            c在执行同步代码块的过程中,执行了锁所属对象的wait方法,这个线程会释放对象锁,而此线程对象会进入线程等待池中,等待被唤醒

    notify和notifyAll的区别

            我们首先得明确,一个对象只有一个锁,参与该对象锁竞争的线程,与参与其他对象锁竞争的线程无关.线程竞争,对应一个对象的一个锁.

            有n个线程参与竞争同一对象的锁,t1率先抢到了锁,锁池中有n-1个线程在等待竞争锁,t1调用了wait()方法t1线程挂起,等待池中有一线程等待被唤醒.t2线程,获得了锁,锁池中有n-2个线程等待竞争锁,t2线程调用了wait方法挂起,等待池中有两个线程等待被唤醒,

    此时t3线程获得了锁,

            如果t3线程在同步代码块中调用的是notify则只会随机唤醒等待池中的一个线程移入锁池中参与锁竞争,有可能是t1,也有可能是t2,等待池中的线程为1

            如果t3线程在同步代码块中调用的notifyAll,则会同时唤醒t1,t2移入锁池中参与竞争,等待池中的线程为零,

    公众号:

  • 相关阅读:
    [LeetCode] Sqrt(x)
    [LeetCode] Rotate Array
    【经典算法】贪心算法
    【经典算法——查找】二分查找
    ARP(Adress Resolution Protocol): 地址解析协议
    【经典算法】分治策略
    [LeetCode] Recover Binary Search Tree
    [LeetCode] Convert Sorted Array to Binary Search Tree
    python数据类型之字典(dict)和其常用方法
    简单了解hash
  • 原文地址:https://www.cnblogs.com/qq289736032/p/10716215.html
Copyright © 2020-2023  润新知