首页 > 编程语言 >java线程原理

java线程原理

时间:2023-09-04 23:04:04浏览次数:41  
标签:java thread Thread void 线程 JVM interrupt 原理

目录

Java线程原理

单词 thread 原意是 线, 在计算机科学引申为 一串执行过程组成的线, 线程是可以由调度程序独立管理的最小编程指令序列. 在一般情况下, 线程属于进程, 同一进程的线程可以同时执行, 共享内存等资源.

具体到 Java 的线程, 即 java.lang.Thread 类, 并不能与操作系统的线程等价. Java 线程由 JVM 实现并调度, 进程切换由 JVM 在用户空间完成, 相比之下开销更小, 同步机制也由 JVM 实现.

为什么需要线程

线程的需求源于系统对同时执行多个任务的需求. 同时, 多线程也能利用上多核CPU进行并行计算.

在操作系统中, 多进程也能完成同时执行多个任务, 但相比进程, 线程更轻量级, 线程之间可以共享内存, 线程的切换比进程切换的开销更小.

使用

线程在调用 start 之后, 该线程进入 RUNNABLE 状态, 在获得的CPU资源后, run 方法将会执行.

线程如何调度本身并不由开发者决定, 对于开发者来说, 线程的使用重点是关注线程需要执行的任务, 即 run 方法中所执行的内容. 在默认情况下, run 方法会构造方法执行传入的 Runnable 类型的任务对象.

下面是一个线程最为基本的使用例子:

public class TestThread {
  public static void main(String[] args) throws InterruptedException {
    var thread =
        new Thread(
            () -> {
              System.out.println("Hello World!");
            });
    thread.start();
    System.out.println("thread started");
    thread.join();
    System.out.println("thread finished");
  }
}
thread started
Hello World!
thread finished

然而实际大多数情况不会这么简单(虽然也不是不能用), 不过本文的主题是线程的实现, 如何使用不是主题, 以后有机会再写写看.

Java源码概要

下文中涉及的源码基于 JDK17.

对于开发者比较重要的一些字段和方法

public class Thread implements Runnable {
    // 线程名称
    private volatile String name;
    // 优先级
    private int priority;
    // 是否守护线程
    private boolean daemon = false;
    // 中断状态
    private volatile boolean interrupted;
    // 运行的任务
    private Runnable target;
    // 线程的所属组
    private ThreadGroup group;
    // 该线程的上下文类加载器, 用于SPI
    private ClassLoader contextClassLoader;
    // 当前线程的 ThreadLocal 变量集合, 该字段由 ThreadLocal 类维护
    ThreadLocal.ThreadLocalMap threadLocals = null;
    // 当前线程的 InheritableThreadLocal 变量集合, 该字段由 InheritableThreadLocal 类维护
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
	// 线程ID
    private final long tid;
    // 线程状态, 可以通过jdk.internal.misc.VM.toThreadState(threadStatus)获取Thread.State类表示的状态
    private volatile int threadStatus;
    
    // 启动线程
    public synchronized void start();
    // 线程运行执行的代码
    public void run();
    // 发出中断信号
    public void interrupt();
    // 线程是否存活, 线程 start 以后, 还未终止就是存活
    public final native boolean isAlive();
    // 另当前线程等待目标线程运行结束
    public final void join();
    
    // 获取当前线程
    public static native Thread currentThread();
    // 获取当前线程的中断状态并重设
    public static boolean interrupted();
}  

Hotspot中的线程

Java的线程需要JVM的线程实现, hotspot中定义了一些线程类型, 其类型关系如下

- Thread
  - JavaThread
    - various subclasses eg CompilerThread, ServiceThread
  - NonJavaThread
    - NamedThread
      - VMThread
      - ConcurrentGCThread
      - WorkerThread
        - GangWorker
    - WatcherThread
    - JfrThreadSampler
    - LogAsyncWriter

一个JVM线程的实例必定为 JavaThreadNonJavaThread.

而在Java中, 通过直接 new Thread 或其自定义子类所创建的Java线程对象, 在调用 start 之后创建是 JavaThread 类型的 JVM 线程.

线程创建和执行及结束的全流程

创建

Java线程对象的创建只需调用 Thread 类或子类的构造方法.

var thread = new Thread(runnable);		

启动

线程在调用 start 方法后启动, 源码如下:

public synchronized void start() {
    // threadStatus 为0时, 对应 Thread.State 为 NEW 的状态
    // 一个线程不能多次 start, 否则抛出异常
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    
    // 将线程加入线程组
    group.add(this);
	
    boolean started = false;
    try {
        // JNI调用, 由JVM启动线程
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
        }
    }
}

JVM 创建的调用 main 方法的线程以及属于 system 线程组的线程不会调用 start 方法.

线程组是一个线程集合, 用于批量管理线程. 线程组有继承关系, 线程组的最终父级是 system 线程组.

以下代码可以打印 system 线程组和子线程组的线程信息:

Thread.currentThread().getThreadGroup().getParent().list();
java.lang.ThreadGroup[name=system,maxpri=10]
    Thread[Reference Handler,10,system]
    Thread[Finalizer,8,system]
    Thread[Signal Dispatcher,9,system]
    Thread[Attach Listener,5,system]
    Thread[Notification Thread,9,system]
    java.lang.ThreadGroup[name=main,maxpri=10]
        Thread[main,5,main]
        Thread[Monitor Ctrl-Break,5,main]
    java.lang.ThreadGroup[name=InnocuousThreadGroup,maxpri=10]
        Thread[Common-Cleaner,8,InnocuousThreadGroup]

可以看到, 主线程属于main线程组, 而由主线程所创建的线程在默认情况下和主线程属于同一个线程组.

start 方法中, 真正的启动线程的在 start0 中.

本地方法 start0 以及 Thread 类中的其他本地方法是由静态方法 Thread.registerNatives() 在类加载的时候注册到JNI环境中的, 在JDK中源码(Thread.c文件)如下:

static JNINativeMethod methods[] = {
    {"start0",           "()V",        (void *)&JVM_StartThread},
    {"setPriority0",     "(I)V",       (void *)&JVM_SetThreadPriority},
    {"yield0",           "()V",        (void *)&JVM_Yield},
    {"sleep0",           "(J)V",       (void *)&JVM_Sleep},
    {"currentCarrierThread", "()" THD, (void *)&JVM_CurrentCarrierThread},
    {"currentThread",    "()" THD,     (void *)&JVM_CurrentThread},
    {"setCurrentThread", "(" THD ")V", (void *)&JVM_SetCurrentThread},
    {"interrupt0",       "()V",        (void *)&JVM_Interrupt},
    {"holdsLock",        "(" OBJ ")Z", (void *)&JVM_HoldsLock},
    {"getThreads",       "()[" THD,    (void *)&JVM_GetAllThreads},
    {"dumpThreads",      "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
    {"getStackTrace0",   "()" OBJ,     (void *)&JVM_GetStackTrace},
    {"setNativeName",    "(" STR ")V", (void *)&JVM_SetNativeThreadName},
    {"scopedValueCache", "()[" OBJ,    (void *)&JVM_ScopedValueCache},
    {"setScopedValueCache", "([" OBJ ")V",(void *)&JVM_SetScopedValueCache},
    {"getNextThreadIdOffset", "()J",   (void *)&JVM_GetNextThreadIdOffset},
    {"findScopedValueBindings", "()" OBJ, (void *)&JVM_FindScopedValueBindings},
    {"ensureMaterializedForStackWalk",
                         "(" OBJ ")V", (void*)&JVM_EnsureMaterializedForStackWalk_func},
};

JNIEXPORT void JNICALL
Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls)
{
    (*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}

start0 对应的就是 JVM_StartThread 的函数指针, 函数源码在 jvm.cpp 文件中.

JVM_StartThread 函数所做的事情主要是:

  • 创建 JavaThread 并初始化

    JVM_StartThread 所创建的C++线程是 JavaThread (C++) 类的实例, 会持有一个函数指针 entry_point, 指向 jvm.cpp 文件中的 thread_entry 函数.

    native_thread = new JavaThread(&thread_entry, sz);
    
    static void thread_entry(JavaThread* thread, TRAPS) {
      HandleMark hm(THREAD);
      Handle obj(THREAD, thread->threadObj());
      JavaValue result(T_VOID);
      // invoke_virtual 调用Java平台线程的run方法
      JavaCalls::call_virtual(&result,
                              obj,
                              vmClasses::Thread_klass(),
                              vmSymbols::run_method_name(),
                              vmSymbols::void_method_signature(),
                              THREAD);
    }
    

    JavaThread 的实例化函数会调用 os::create_thread 创建操作系统线程, 其在 windows 平台的实现在 os_windows.cpp 文件中.

    JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) : JavaThread() {
      _jni_attach_state = _not_attaching_via_jni;
      // 设置线程要执行的函数
      set_entry_point(entry_point);
      os::ThreadType thr_type = os::java_thread;
      // CompilerThread 是JIT编译器的线程
      thr_type = entry_point == &CompilerThread::thread_entry ? os::compiler_thread :
                                                                os::java_thread;
      // 创建os线程
      os::create_thread(this, thr_type, stack_sz);
    }
    
    // os_windows.cpp
    bool os::create_thread(Thread* thread, ThreadType thr_type,
                           size_t stack_size) {
      unsigned thread_id;
      // 创建和初始化OS线程
      OSThread* osthread = new (std::nothrow) OSThread();
      if (osthread == nullptr) {
        return false;
      }
      osthread->set_state(ALLOCATED);
      // jdk 中断事件支持(windows平台)
      HANDLE interrupt_event = CreateEvent(nullptr, true, false, nullptr);
      if (interrupt_event == nullptr) {
        delete osthread;
        return false;
      }
      osthread->set_interrupt_event(interrupt_event);
      // Java 平台Thread -> JVM JavaThread -> OS Thread
      thread->set_osthread(osthread);
    
      
      if (stack_size == 0) {
        switch (thr_type) {
        case os::java_thread:
          // -Xss可以设置最小栈大小
          if (JavaThread::stack_size_at_create() > 0) {
            stack_size = JavaThread::stack_size_at_create();
          }
          break;
        case os::compiler_thread:
          if (CompilerThreadStackSize > 0) {
            stack_size = (size_t)(CompilerThreadStackSize * K);
            break;
          }
        case os::vm_thread:
        case os::gc_thread:
        case os::asynclog_thread:
        case os::watcher_thread:
          if (VMThreadStackSize > 0) stack_size = (size_t)(VMThreadStackSize * K);
          break;
        }
      }
      
      // windows的线程flag
      const unsigned initflag = CREATE_SUSPENDED | STACK_SIZE_PARAM_IS_A_RESERVATION;
      // 线程句柄
      HANDLE thread_handle;
      int limit = 3;
      do {
        thread_handle =
          // windows创建线程的系统调用
          (HANDLE)_beginthreadex(nullptr,
                                 (unsigned)stack_size,
                                 // 操作系统线程将执行 thread_native_entry
                                 &os::win32::thread_native_entry,
                                 // thread_native_entry 的参数
                                 thread,
                                 // 创建线程的flag, 此处是挂起并预留栈空间
                                 initflag,
                                 &thread_id);
      } while (thread_handle == nullptr && errno == EAGAIN && limit-- > 0);
    
      ResourceMark rm;
      char buf[64];
      if (thread_handle != nullptr) {
        log_info(os, thread)("Thread \"%s\" started (tid: %u, attributes: %s)",
                             thread->name(), thread_id,
                             describe_beginthreadex_attributes(buf, sizeof(buf), stack_size, initflag));
      } else {
        log_warning(os, thread)("Failed to start thread \"%s\" - _beginthreadex failed (%s) for attributes: %s.",
                                thread->name(), os::errno_name(errno), describe_beginthreadex_attributes(buf, sizeof(buf), stack_size, initflag));
        log_info(os, thread)("Number of threads approx. running in the VM: %d", Threads::number_of_threads());
        LogStream st(Log(os, thread)::info());
        os::print_memory_info(&st);
      }
      // 创建失败处理
      if (thread_handle == nullptr) {
        thread->set_osthread(nullptr);
        delete osthread;
        return false;
      }
        
      // 创建成功设置OS Thread字段
      osthread->set_thread_handle(thread_handle);
      osthread->set_thread_id(thread_id);
      osthread->set_state(INITIALIZED);
      return true;
    }
    
  • 调用 Thread::start 启动线程

    Thread::start 设置 Java 的 Thread 类的 threadState 字段的值为 RUNNABLE 对应的值, 并通过 os::start_thread 运行操作系统线程.

    void Thread::start(Thread* thread) {
      if (thread->is_Java_thread()) {
        // 设置Java平台线程对象的状态
        java_lang_Thread::set_thread_status(JavaThread::cast(thread)->threadObj(),
                                            JavaThreadStatus::RUNNABLE);
      }
      // 启动OS线程
      os::start_thread(thread);
    }
    
    void os::start_thread(Thread* thread) {
      OSThread* osthread = thread->osthread();
      osthread->set_state(RUNNABLE);
      // 启动线程, 不同平台实现不同
      pd_start_thread(thread);
    }
    

运行和退出

操作系统线程运行的是 thread_native_entry 函数, 该函数的参数是前一步所创建的 C++ 线程对象.

thread_native_entry 是JVM线程执行的函数入口, 该函数内部调用了线程类的 call_run 函数, 该函数会按顺序调用线程的 pre_run, run , post_run 函数.

对于 JavaThread 的实现来说:

  • pre_run 函数为空什么也不做

  • run 函数按顺序执行一下过程:

    • 初始化 TLAB
    • 调用 thread_main_inner 方法, thread_main_inner 方法中将会调用 JavaThread 所持有的 entry_point 函数指针. entry_pointthread_entry 的函数指针, 该函数将会通过 invoke_virtual 指令调用Java平台的线程类对象的 run 方法.
    void JavaThread::run() {
      // 初始化TLAB
      initialize_tlab();
      // ... 其它还没搞懂的代码
      // 执行构造 JavaThread 时的函数指针 entry_point
      thread_main_inner();
    }
    
    void JavaThread::thread_main_inner() {
      assert(JavaThread::current() == this, "sanity check");
      assert(_threadObj.peek() != nullptr, "just checking");
    
      if (!this->has_pending_exception()) {
        {
          ResourceMark rm(this);
          this->set_native_thread_name(this->name());
        }
        HandleMark hm(this);
        // 执行构造 JavaThread 时传入的函数指针
        this->entry_point()(this, this);
      }
    
      DTRACE_THREAD_PROBE(stop, this);
    }
    
  • post_run 函数

    • 调用 JavaThreadexit 函数, 如果有异常, exit 函数将会对退出的异常进行处理, 在正常退出的情况下, 将会通过 invoke_virtual 指令调用Java平台的线程类对象的 exit 方法
    • 设置Java平台线程的状态为终止
    • 通知所有 join 了当前线程的线程
    • 其它清理工作
    void JavaThread::post_run() {
      this->exit(false);
      this->unregister_thread_stack_with_NMT();
      // jstack会显示线程SMR信息
      this->smr_delete();
    }
    
    void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
      /* 其它代码*/
      if (!destroy_vm) {
        if (uncaught_exception.not_null()) {
          EXCEPTION_MARK;
          
          // 调用Java线程的dispatchUncaughtException方法处理未捕获的异常
          Klass* thread_klass = vmClasses::Thread_klass();
          JavaValue result(T_VOID);
          JavaCalls::call_virtual(&result,
                                  threadObj, thread_klass,
                                  vmSymbols::dispatchUncaughtException_name(),
                                  vmSymbols::throwable_void_signature(),
                                  uncaught_exception,
                                  THREAD);
          if (HAS_PENDING_EXCEPTION) {
            ResourceMark rm(this);
            jio_fprintf(defaultStream::error_stream(),
                        "\nException: %s thrown from the UncaughtExceptionHandler"
                        " in thread \"%s\"\n",
                        pending_exception()->klass()->external_name(),
                        name());
            CLEAR_PENDING_EXCEPTION;
          }
        }
          
        if (!is_Compiler_thread()) {
          int count = 3;
          while (java_lang_Thread::threadGroup(threadObj()) != NULL && (count-- > 0)) {
            EXCEPTION_MARK;
            // 调用Java线程的exit方法
            JavaValue result(T_VOID);
            Klass* thread_klass = vmClasses::Thread_klass();
            JavaCalls::call_virtual(&result,
                                    threadObj, thread_klass,
                                    vmSymbols::exit_method_name(),
                                    vmSymbols::void_method_signature(),
                                    THREAD);
            CLEAR_PENDING_EXCEPTION;
          }
        }
          
        if (JvmtiExport::should_post_thread_life()) {
          JvmtiExport::post_thread_end(this);
        }
      } else {
        assert(!is_terminated() && !is_exiting(), "must not be exiting");
      }
      /* 其它代码 */
        
      // notify所有通过 join 阻塞的线程
      ensure_join(this);
      /* 其它代码 */
    }
    
    private void exit() {
        if (threadLocals != null && TerminatingThreadLocal.REGISTRY.isPresent()) {
            TerminatingThreadLocal.threadTerminated();
        }
        if (group != null) {
            group.threadTerminated(this);
            group = null;
        }
        /* Aggressively null out all reference fields: see bug 4006245 */
        target = null;
        /* Speed the release of some of these resources */
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }
    

线程 join 的原理

Thread 类的 join 可以让执行调用时的线程阻塞等待调用该方法的线程完成.

var thread = new Thread(() -> System.out.println("running"));
thread.start();
thread.join(); // 阻塞直到线程执行完成(前文提到的 `post_run` 函数的退出通知完成)
System.out.println("finished");

join 方法源码:

源码注释:

最多等待 millis 毫秒到线程终止, 如果时间为0则永远等待.

通过以 this.isAlive 为条件循环调用 this.wait 实现, 当线程终止时, 将调用 this.notifyAll 方法.

建议应用程序不要在 Thread 实例上使用 wait, notifynotifyAll.

public final synchronized void join(final long millis) throws InterruptedException {
    if (millis > 0) {
        if (isAlive()) {
            final long startTime = System.nanoTime();
            long delay = millis;
            do {
                wait(delay); // this.wait(millis) 锁住的就是当前 thread 对象的监视器
            } while (isAlive() && (delay = millis -
                    TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)) > 0);
        } // else 如果线程已经终止, 就直接返回
    } else if (millis == 0) {
        while (isAlive()) {
            wait(0);
        } // else 如果线程已经终止, 就直接返回
    } else {
        throw new IllegalArgumentException("timeout value is negative");
    }
}

可以看到, join 本质上就是调用了当前线程从 Object 类继承的 wait 方法, 如果被唤醒, 如果线程还未结束(带超时时间的则会也判断是否超时), 将继续调用 wait 阻塞, 直至线程完成, 而线程完成的时候, 会由 JVM 对该线程的监视器发出通知唤醒.

那么在看完上面的源码后回答一个问题: 对当前线程 Thread.currentThread().join() 会发生什么? 答案是永远阻塞, 除非调用该线程的 interrupt 方法.

public class TestJoin {
  public static void main(String[] args) throws InterruptedException {
    var mainThread = Thread.currentThread();
    var thread =
        new Thread(
            () -> {
              sleep(1000);
              System.out.println("before notify");
              synchronized (mainThread) {
                mainThread.notify();
              }
              sleep(1000);
              System.out.println("notified");
              System.out.println("before interrupt");
              mainThread.interrupt();
            });
    thread.start();
    try {
      mainThread.join();
    } catch (InterruptedException e) {
      System.out.println("interrupt");
    }
  }

  private static void sleep(long millis) {
    try {
      Thread.sleep(millis);
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
  }
}

如果你在 join 方法中的 wait(0) 和 那一行以及上面这段代码的 mainThread.notify() 打上断点, 并Debug 执行上面这段代码, 可以发现首次 wait(0) 之后, 再执行 notify, mainThread 又会继续运行并再次调用 wait(0), 而在 interrupt 之后, wait(0) 就会抛出 InterruptedException, 在被捕获之后执行异常处理.

线程 interrupt 的原理

Java线程的 interrupt 方法可以中断线程, 这种中断并非中止, 中断只能中断线程的特点操作. 源码如下:

源码注释(机翻警告):

中断该线程。
除非当前线程正在中断自身(这始终是允许的),否则将调用该线程的checkAccess方法,这可能会导致抛出SecurityException 。
如果此线程在调用Object类的wait() 、 wait(long)或wait(long, int)方法或join() 、 join(long) 、 join(long, int)时被阻止该类的 、 sleep(long)或sleep(long, int)方法,则其中断状态将被清除,并且将收到InterruptedException 。
如果该线程在InterruptibleChannel上的 I/O 操作中被阻塞,则通道将被关闭,线程的中断状态将被设置,并且线程将收到java.nio.channels.ClosedByInterruptException 。
如果该线程在java.nio.channels.Selector中被阻塞,则该线程的中断状态将被设置,并且它将立即从选择操作返回,可能返回一个非零值,就像调用选择器的wakeup方法一样。
如果前面的条件都不成立,则该线程的中断状态将被设置。
中断不活动的线程不需要产生任何效果。

public void interrupt() {
    if (this != Thread.currentThread()) {
        checkAccess();

        // 线程可能阻塞在调用 I/O
        synchronized (blockerLock) {
            // 不为空时, 代表线程阻塞在 I/O
            Interruptible b = blocker;
            if (b != null) {
                interrupted = true;
                interrupt0();  // inform VM of interrupt
                b.interrupt(this);
                return;
            }
        }
    }
    interrupted = true;
    // inform VM of interrupt
    interrupt0();
}

interrupt 可以中断的线程操作有:

  1. 会抛出 InterruptedException 的方法, 包括 wait, join, sleep 等等.

    这些方法在被中断后, 将会抛出 InterruptedException, 并且其中断标识即 Thread 类的 interrupted 字段将被设置为 false.

  2. 会抛出 ClosedByInterruptException 的方法, 即 InterruptibleChannel 的各种子类的 I/O 操作.

    这些方法在被中断后, 将会抛出 ClosedByInterruptException, 并且其中断标识将设置为 true.

    private static void testChannelInterrupt() throws InterruptedException {
      var thread =
          new Thread(
              () -> {
                try {
                  var serverSocketChannel = ServerSocketChannel.open();
                  serverSocketChannel.bind(new InetSocketAddress(8080));
                  serverSocketChannel.accept();
                } catch (ClosedByInterruptException e) {
                  // channel interrupt true
                  System.out.println("channel interrupt " + Thread.currentThread().isInterrupted());
                } catch (IOException e) {
                  throw new RuntimeException(e);
                }
              });
      thread.start();
      Thread.sleep(1000);
      thread.interrupt();
      Thread.sleep(1000);
    }
    
  3. java.nio.channels.Selector 的阻塞操作. 在中断后, 中断标识将为 true, 但并不会抛出异常, 而是直接返回. 与 Channel 的区别在于对 Interruptible 实现的区别.

    // AbstractSelector 类阻塞I/O操作会调用该方法
    protected final void begin() {
        if (interruptor == null) {
            // 线程中断时的操作
            interruptor = new Interruptible() {
                    public void interrupt(Thread ignore) {
                        AbstractSelector.this.wakeup();
                    }};
        }
        AbstractInterruptibleChannel.blockedOn(interruptor);
        Thread me = Thread.currentThread();
        if (me.isInterrupted())
            interruptor.interrupt(me);
    }
    
    

​ 关于 Interruptible 下文会说明.

java.io 的 I/O 操作并不会会被 interrupt 中断.

如果在目标线程调用这些方法之前调用 interrupt 线程, 那么这些操作同样会被中断并产生对应的结果.

private static void testInterruptBeforeSleep() {
    // false
    System.out.println(Thread.currentThread().isInterrupted());
    // true
    Thread.currentThread().interrupt();
    System.out.println(Thread.currentThread().isInterrupted());
    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      // interrupted false
      System.out.println("interrupted " + Thread.currentThread().isInterrupted());
    }
}

可中断IO的实现

前文提到的第 2 和 第 3 种可中断的操作, 本质都是可中断I/O, 其实现借助于 InterruptibleThread 类的 blocker 字段.

/* The object in which this thread is blocked in an interruptible I/O
 * operation, if any.  The blocker's interrupt method should be invoked
 * after setting this thread's interrupt status.
 */
private volatile Interruptible blocker;
private final Object blockerLock = new Object();

/* Set the blocker field; invoked via jdk.internal.access.SharedSecrets
 * from java.nio code
 */
static void blockedOn(Interruptible b) {
    Thread me = Thread.currentThread();
    synchronized (me.blockerLock) {
        me.blocker = b;
    }
}

在调用可中断的阻塞I/O操作前, 调用方将通过 blockedOn 方法设置线程的 blocker 字段.

Interruptible 只有一个 interrupt 方法, 本质是一个回调, 通过设置中断时的回调, 在调用线程的 interrupt 方法的时候就可以调用该回调中断 I/O 操作.

在调用 blockedOn 前, 可能线程就已经被中断了, 因此, 在 blockedOn 完成后都需要检查线程中断状态, 如果已经中断就直接调用 Interruptibleinterrupt 方法.

例如 AbstractInterruptibleChannelbegin 方法源码:

protected final void begin() {
    if (interruptor == null) {
        interruptor = new Interruptible() {
                public void interrupt(Thread target) {
                    synchronized (closeLock) {
                        if (closed)
                            return;
                        closed = true;
                        interrupted = target;
                        try {
                            // 线程 interrupt 的时候关闭 Channel
                            AbstractInterruptibleChannel.this.implCloseChannel();
                        } catch (IOException x) { }
                    }
                }};
    }
    blockedOn(interruptor); // 设置线程的 blocker
    Thread me = Thread.currentThread();
    if (me.isInterrupted()) // 检查中断状态
        interruptor.interrupt(me); // 调用 Interruptible 的 interrupt 方法
}

因此, 也可以自己来实现一个 Interruptible:

public class TestInterrupt {
  public static void main(String[] args) throws InterruptedException, IOException {
    SharedSecrets.getJavaLangAccess()
        .blockedOn(t -> System.out.println("Interruptible: " + t.isInterrupted()));
    var mainThread = Thread.currentThread();
    new Thread(mainThread::interrupt).start();
    while (!Thread.interrupted()) {
      System.out.println("main thread is not interrupted");
    }
  }
}

这段代码需要增加 --add-exports=java.base/jdk.internal.access=ALL-UNNAMED --add-exports=java.base/sun.nio.ch=ALL-UNNAMED 才可以编译运行.

在输出若干次 main thread is not interrupted 后将输出 Interruptible: true.

interrupt0 的实现

本地方法 interrupt0 对应的 CPP 函数指针将调用 JavaThreadinterrupt 函数.

void JavaThread::interrupt() {
  // All callers should have 'this' thread protected by a
  // ThreadsListHandle so that it cannot terminate and deallocate
  // itself.
  debug_only(check_for_dangling_thread_pointer(this);)

  // For Windows _interrupt_event
  WINDOWS_ONLY(osthread()->set_interrupted(true);)

  // For Thread.sleep
  _SleepEvent->unpark();

  // For JSR166 LockSupport.park
  parker()->unpark();

  // For ObjectMonitor and JvmtiRawMonitor
  _ParkEvent->unpark();
}

_SleepEvent_ParkEvent 都是 ParkEvent 类型, parker 函数则会返回一个 Parker 类的对象, 他们源码都有比较多的说明.

ParkEventPacker 都设计了一对 park - unpark 操作, park 可以让线程挂起直到 unpark.

以此实现了 Java 平台的 sleep, wait, join 以及 juc 中的一些可中断方法.

private static void testJucInterrupt() throws InterruptedException {
  var reentrantLock = new ReentrantLock();
  reentrantLock.lock();
  var thread =
      new Thread(
          () -> {
            try {
              System.out.println("thread interrupt " + Thread.currentThread().isInterrupted());
              reentrantLock.lockInterruptibly();
            } catch (Exception e) {
              System.out.println("lock interrupt");
            }
          });
  thread.start();
  thread.interrupt();
  Thread.sleep(1000);
}

另外, Thread 类的 interrupted 会静态方法读取线程的中断状态并重设中断状态.

public static boolean interrupted() {
    Thread t = currentThread();
    boolean interrupted = t.interrupted;
    // We may have been interrupted the moment after we read the field,
    // so only clear the field if we saw that it was set and will return
    // true; otherwise we could lose an interrupt.
    if (interrupted) {
        t.interrupted = false;
        clearInterruptEvent();
    }
    return interrupted;
}

标签:java,thread,Thread,void,线程,JVM,interrupt,原理
From: https://www.cnblogs.com/wymc/p/17678209.html

相关文章

  • Java线程状态
    目录Java线程状态NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITING测试Java线程状态Java中有六个线程状态://Thread.StatepublicenumState{NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED;}这些线程状态表示线程的生命周期,其状态变......
  • Java实现多种单例模式
    packagecom.java3y.austin.test.Mode;importjava.util.HashSet;importjava.util.concurrent.atomic.AtomicBoolean;//饿汉式单例classSingleton1{privateSingleton1(){}privatestaticfinalSingleton1singleton1=newSingleton1();publ......
  • 《C++》11新特性--多线程
    thread创建线程,不会阻塞主线程thread成员函数voidthread::join();阻塞线程,当前线程执行完毕才会往下执行boolthread::joinble();线程是否可以连接,返回voidthread::detach();分离主线程和子线程的关联voidprintI(){for(size_ti=0;i<100;i++){std::c......
  • 如何在Java中连接MySQL数据库并实现增删改查操作
    要在Java中连接MySQL数据库并实现增删改查操作,你需要遵循以下步骤:导入所需的Java库:你需要导入Java中与MySQL连接和操作相关的库。常用的库包括JDBC(JavaDatabaseConnectivity)和MySQL驱动程序。importjava.sql.*;建立数据库连接:使用JDBC连接MySQL数据库,你需要提供数据库的URL、用......
  • SpringBoot项目报java.lang.IllegalStateException: Cannot load configuration class
    报错摘要:java.lang.IllegalStateException:Cannotloadconfigurationclass:com.imooc.demo.DemoApplication,Causedby:java.lang.ExceptionInInitializerError:null,Causedby:org.springframework.cglib.core.CodeGenerationException:java.lang.reflect.I......
  • ##线程面试题##
    一.java中线程实现几种实现方式在Java中实现多线程一共有四种方式:(1)继承Thread类(2)实现Runable接口(3)实现Callable接口(4)线程池1.继承java.lang.Thread,重写run方法,启动线程,调用start()方法>2.实现java.lang.Runnable接口,实现run方法3.实现Callable接口(JDK8新特性)该方法效率较低,......
  • JavaWeb项目3.0之后创建web项目
    在上一篇文章中我们看到创建web项目的过程,但是在web3.0之后有了注解的加入,可以更简单的创建Web项目。选择自己创建的maven项目,填写项目名称。 在项目中添加web模块 添加对应的打包形式 配置tomcat 添加对应模块  最为重要的我们不需要web.xml的繁琐......
  • 线程池至少需要线程数——23秋招招行网络科技第一批技术测评_后端(c++)
    题目:有n个计划,每个计划有开始,结束时间,求线程池最少需要多少个线程?例:输入:2,[[1,2],[3,4]],输出:1输入:2, [[1,3],[2,4]],输出:2 思路:贪心算法PS:其实我不是很理解下面代码第11行,分别对a,b数组排序1#include<bits/stdc++.h>2usingnamespacestd;34intma......
  • 动手实践-AT模式原理
       ......
  • java基础-数组-day06
    1.引入数组录入10个学生的成绩求和求平均importjava.util.Scanner;publicclassTestArray01{publicstaticvoidmain(String[]args){ Scannersc=newScanner(System.in); intsum=0; for(inti=1;i<=10;i++){ System.out.println("请输入第"+i+"......