今天看啥  ›  专栏  ›  matoujun

java线程基本的问题,你理解的对吗?

matoujun  · 掘金  ·  · 2018-10-10 07:27

本节总结一些线程相关的Q&A,涉及到java多线程操作的基本方法。

  1. 如何创建一个线程。

    有两种基本的方式,一是继承Thread类,并重写run方法;二是实现runnable或callable接口,然后创建一个thread,并以该接口作为参数。

    如果使用Executors框架,可以分离线程的管理和任务的执行,传递一个runnable或callable接口启动一个线程:

    ExecutorService service = Executors.newCachedThreadPool();

    service.execute(runnable);

  2. 请从JDK和JVM的视角描述线程启动的详细过程。

    调用Thread类的start方法启动一个线程,需注意调用线程的run方法或者runnable或callable接口的run方法并不能启动线程,run方法的调用和普通的blocking方法调用无任何区别。从JVM角度看线程的启动过程有一些复杂,详细过程如下:

    1. 通过Thread类的start方法调用native start0方法;

      public synchronized void start() {

      …boolean started = false;

      try {

      start0();

      started = true;

      } finally {

      }

      }

    2. 通过JNI,start0方法调用JVM_StartThread,该方法负责创建并启动一个kernel线程,因为每一个java线程对应一个kernel线程。

      Jvm.cpp:

      JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))

      native_thread = new JavaThread(&thread_entry, sz);

      Thread::start(native_thread);

      JVM_END

    3. new一个JavaThread对象会创建一个kernel线程,代码如下所示:

      Os_bsd.cpp:

      bool os::create_thread(Thread* thread, ThreadType thr_type,

      size_t req_stack_size) {

      int ret = pthread_create(&tid, &attr, (void* (*)(void*)) thread_native_entry, thread);

      }

    4. 创建kernel线程时,传入的thread_native_entry方法负责启动所有的java线程,该方法会一直阻塞直到新创建的kernel线程状态设置为runnable,代码如下:

      static void *thread_native_entry(Thread *thread) {

      {

      MutexLockerEx ml(sync, Mutex::_no_safepoint_check_flag);

      osthread->set_state(INITIALIZED);

      sync->notify_all();

      while (osthread->get_state() == INITIALIZED) {

      sync->wait(Mutex::_no_safepoint_check_flag);

      }

      }

      thread->run();

      }

    5. 而启动kernel线程的方法参见b)中的Thread::start(native_thread),该方法会设置kernel线程状态为runnable,如下所示:

      os.cpp

      void os::start_thread(Thread* thread) {

           OSThread* osthread = thread->osthread();

           osthread->set_state(RUNNABLE);

           pd_start_thread(thread);

          }

       f. 当kernel线程设置为runnable状态后,thread_native_entry方法调用            thread->run(),该方法调用JavaThread类的run方法,如下:

           // The first routine called by a new Java thread

            void JavaThread::run() {

            // initialize thread-local alloc buffer related fields

            this->initialize_tlab();

            // used to test validity of stack trace backs

            this->record_base_of_stack_pointer();

            // Record real stack base and size.

            this->record_stack_base_and_size();

            this->create_stack_guard_pages();

            this->cache_global_variables();

            …

            thread_main_inner();

            …

            }

    g.thread_main_inner方法调用entry_point方法,代码如下:

        void JavaThread::thread_main_inner() {

        …

        this->entry_point()(this, this);

        }

        …

        }

    h.entry_point()方法指向thread_entry,其中包括了JavaCalls::call_virtual,该方法即为JVM调用java方法的入口,代码如下:

        static void thread_entry(JavaThread* thread, TRAPS) {

        …

        JavaCalls::call_virtual(&result,

        obj,

        KlassHandle(THREAD, SystemDictionary::Thread_klass()),

        vmSymbols::run_method_name(),

        vmSymbols::void_method_signature(),

        THREAD);

        }

    自此,java thread启动成功。由此可见,thread的start方法间接调用执行了run方法,直接是由JVM来调用的。

    1. 请描述线程的状态机。

      线程状态机如下图所示:

       引自https://www.uml-diagrams.org/examples/java-6-thread-state-machine-diagram-example.htm

      需注意java的线程状态和内核状态不能一一对应,比如:java线程的NEW状态就对应了内核线程的allocated,initialized两种状态。内核线程的状态有:

      enum ThreadState {

      ALLOCATED, // Memory has been allocated but not initialized

      INITIALIZED, // The thread has been initialized but yet started

      RUNNABLE, // Has been started and is runnable, but not necessarily running MONITOR_WAIT, // Waiting on a contended monitor lock

      CONDVAR_WAIT, // Waiting on a condition variable

      OBJECT_WAIT, // Waiting on an Object.wait() call

      BREAKPOINTED, // Suspended at breakpoint

      SLEEPING, // Thread.sleep()

      ZOMBIE // All done, but not reclaimed yet

      };

      java线程的状态有:

      public enum State {

      NEW,

      RUNNABLE,

      BLOCKED,

      WAITING,

      TIMED_WAITING,

      TERMINATED;

      }

      上述状态的字段说明在JVM和JDK源码中已经详细给出可参考Thread.cpp和Thread.java。有一点需注意:线程的状态和线程是否拥有锁的区别,例如线程处于WAITING状态时,线程可能拥有锁(调用sleep方法)也可能已经释放锁(调用wait方法)。

    2. 请说出wait/notify(notifyAll)和 await/signal(signalAll)之间的异同。

      1. Wait/notify是对象方法, 而await/signal是Condition接口方法。

      2. 优先使用await/signal,因为Condition接口把监视器方法与对象分离开,可以在一个Lock上绑定多个await/signal。但对于内置锁(intrinsic lock),即synchronized绑定的监视器,只有一个“Condition”可用。

      3. 两者都支持time参数,wait/await(milliseconds)、wait/await(milliseconds, nanos)。而await除了上述方法,还支持awaitUntil(Date)。

      4. 在wait和await之前调用Notify和signal时,信号都会丢失。

      总之,在Lock代替synchronized的地方,使用Condition代替对象监视器方法。

    3. 请说出sleep和yield的异同

      相同点:sleep, yield都是线程方法。如果当前线程持有锁,两者都会回释放锁。事实上,根据java规范,sleep和yield没有任何线程同步相关的作用。

      不同点:当前线程调用sleep方法后,线程处于waiting或者timed waiting;而yield只是暗示JVM当前线程想要休息一会,不做其他任何事情,由JVM的线程调度器决定如何做下一步,因此当前线程仍处于runnable状态,只不过不是running,而是ready。Sleep方法执行完后,线程可能进入runnable或者终结;而yield结束后,线程仍处于runnable。Sleep会抛出InterruptedException,而yield不会。




    原文地址:访问原文地址
    快照地址: 访问文章快照