引言 在JDK17(或以上版本)中,Thread类提供了一组常用的API,用于管理线程的创建、启动、暂停、恢复和销毁等操作。本文从api、源码、编程示例等方面详细说明Thread常用函数的使用和注意事项。
flowchart LR
    A[Thread常用API使] --> sleep
    A --> yield
    A --> 设置线程的优先级
    A --> 获取线程ID
    A --> 获取当前线程
    A --> 设置线程上下文类加载器
    A --> interrupt
    A --> join
    A --> 关闭线程 
线程 sleep 
使当前正在执行的线程暂停(挂起)指定的毫秒数。但受系统计时器和调度程序的精度和准确性限制。 
线程不会失去任何monitor(监视器)的所有权。 
每个线程的休眠互不影响,Thread.sleep 只会导致当前线程进入指定时间的休眠。 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public  static  native  void  sleep (long  millis)  throws  InterruptedException;public  static  void  sleep (long  millis, int  nanos) throws  InterruptedException {    if  (millis < 0 ) {         throw  new  IllegalArgumentException ("timeout value is negative" );     }     if  (nanos < 0  || nanos > 999999 ) {         throw  new  IllegalArgumentException (                             "nanosecond timeout value out of range" );     }     if  (nanos > 0  && millis < Long.MAX_VALUE) {         millis++;     }     sleep(millis); } 
通过测试发现 Thread.sleep 之间互不影响。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public  class  ThreadSleepTest  {    public  static  void  main (String[] args)  throws  Exception{         Thread  thread1  =  new  Thread (()->{             int  i  =  0 ;             while (i<10 ){                 System.out.println("thread demo start " +i);                 i++;                 try  {                     Thread.sleep(1000 );                 } catch  (InterruptedException e) {                     throw  new  RuntimeException (e);                 }             }         });         thread1.start();         System.out.println("thread main start " );         Thread.sleep(2000 );         System.out.println("thread main end " );     } } 
输出结果如下:
1 2 3 4 5 6 thread main start  thread demo start 0 thread demo start 1 thread main end  thread demo start 2 thread demo start 3 
除此之外可以使用 java.util.concurrent.TimeUnit 类来更简单的实现指定时间的休眠,后续源码使用该类来进行休眠线程。例子代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package  engineer.concurrent.battle.abasic;import  java.util.concurrent.TimeUnit;public  class  ThreadSleepTimeUnitTest  {    public  static  void  main (String[] args)  throws  Exception{         System.out.println("thread main start " );         TimeUnit.SECONDS.sleep(1 );         TimeUnit.MILLISECONDS.sleep(500 );         TimeUnit.MINUTES.sleep(1 );         System.out.println("thread main end " );     } } public  void  sleep (long  timeout)  throws  InterruptedException {    if  (timeout > 0 ) {         long  ms  =  toMillis(timeout);         int  ns  =  excessNanos(timeout, ms);         Thread.sleep(ms, ns);     } } 
线程 yield Thread.yield()是一个提示,用于告诉调度程序当前线程愿意放弃使用处理器。调度程序可以选择忽略这个提示。Yield是一种试图改善线程之间相对进程的启发式方法,否则它们会过度利用CPU。它的使用应该与详细的分析和基准测试结合起来,以确保它确实产生了预期的效果。
这种方法适用场景很少。它有助于调试或测试,以帮助重现由于竞态条件而引起的错误。在设计并发控制结构时,例如java.util.concurrent.locks包中的结构,它也可能有用。
调用Thread.yield()函数会将当前线程从RUNNING状态切换到RUNNABLE状态。
1 public  static  native  void  yield () ;
测试代码如下,在cpu资源不紧张的情况下输出仍然是乱序的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package  engineer.concurrent.battle.abasic;import  java.util.stream.IntStream;public  class  ThreadYieldTest  {    public  static  void  main (String[] args)  throws  Exception{         System.out.println("thread main start " );         IntStream.range(0 , 2 ).mapToObj(ThreadYieldTest::create).forEach(Thread::start);         System.out.println("thread main end " );     }     private  static  Thread create (int  i)  {         return  new  Thread (() -> {             if (i == 0  ){                 Thread.yield();             }             System.out.println("thread "  + i + " start " );         });     } } 
输出结果:
1 2 3 4 thread main start  thread main end  thread 0 start  thread 1 start  
Thread.yield() 和 Thread.sleep() 方法之间的联系和差异如下:
联系:
Thread.yield() 和 Thread.sleep() 都会使当前线程暂停执行,让出CPU资源给其他线程。 
Thread.yield() 和 Thread.sleep() 都不会释放当前线程所占用的锁。 
 
差异:
Thread.yield() 方法只是暂停当前线程的执行,让出CPU资源给其他线程,但不会进入阻塞状态。可能导致CPU进行上下文切换。 
Thread.sleep() 方法会使当前线程暂停指定的时间,并进入阻塞状态,直到休眠时间结束或者被其他线程打断。 
Thread.sleep()具有较高的可靠性,可以确保至少暂停指定的时间。Thread.yield()则不能保证暂停。 
 
设置线程的优先级 
java.lang.Thread#setPriority 修改线程的优先级java.lang.Thread#getPriority 获取线程的优先级 
java.lang.Thread#setPriority 修改线程的优先级实现过程如下:
调用此线程的checkAccess方法,不带任何参数。这可能会导致抛出一个SecurityException异常。 
线程的优先级被设置为指定的newPriority和线程所属线程组允许的最大优先级中较小的值。 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public  final  void  setPriority (int  newPriority)  {    ThreadGroup g;          checkAccess();     if  (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {         throw  new  IllegalArgumentException ();     }     if ((g = getThreadGroup()) != null ) {                  if  (newPriority > g.getMaxPriority()) {             newPriority = g.getMaxPriority();         }         setPriority0(priority = newPriority);     } } public  final  int  getPriority ()  {         return  priority; } private  native  void  setPriority0 (int  newPriority) ;
进程有进程的优先级,线程同样也有优先级,理论上是优先级比较高的线程会获取优先被 CPU 调度的机会,但是事实上设置线程的优先级同样也是一个 hint 操作,具体如下。
对于 root 用户,它会 hint 操作系统你想要设置的优先级别,否则它会被忽略。 
如果 CPU 比较忙,设置优先级可能会获得更多的 CPU 时间片,但是闲时优先级的高低几乎不会有任何作用。 
 
所以不要使用线程的优先级进行某些特定业务的绑定,业务执行的顺序应该还是要使用同步执行方法来保证。
测试例子如下,线程之间会交替输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package  engineer.concurrent.battle.abasic;public  class  ThreadPriorityTest  {    public  static  void  main (String[] args)  throws  Exception{         Thread  t1  =  ThreadPriorityTest.create("t1" );         t1.setPriority(1 );         Thread  t2  =  ThreadPriorityTest.create("t2" );         t2.setPriority(10 );         t1.start();         t2.start();     }     private  static  Thread create (String name)  {         return  new  Thread (() -> {             while  (true ) {                 System.out.println("thread "  + name );             }         });     } } 
获取线程ID 返回此线程的标识符。线程ID是一个正的long数字,在创建此线程时生成。线程ID是唯一的,并在其生命周期内保持不变。当一个线程终止时,该线程ID可能会被重新使用。
1 2 3 public  long  getId ()  {    return  tid; } 
获取当前线程 java.lang.Thread#currentThread 方法被大多数框架使用,像是SpringMVC、MyBatis这些。调用该函数会返回当前正在执行的线程对象。
1 2 @IntrinsicCandidate public  static  native  Thread currentThread () ;
测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package  engineer.concurrent.battle.abasic;public  class  ThreadCurrentTest  {    public  static  void  main (String[] args)  throws  Exception{         Thread  t1  =  new  Thread () {             @Override              public  void  run ()  {                 System.out.println(this  == Thread.currentThread());             }         };         t1.start();         System.out.println(Thread.currentThread().getName());     } } 
设置线程上下文类加载器 
java.lang.Thread#getContextClassLoader 返回该线程的上下文ClassLoader。上下文ClassLoader由创建线程的对象提供,用于在此线程中运行的代码在加载类和资源时使用。如果未设置(通过setContextClassLoader()方法),则默认为父线程的ClassLoader上下文。原始线程的上下文ClassLoader通常设置为用于加载应用程序的类加载器。java.lang.Thread#setContextClassLoader 设置此线程的上下文ClassLoader。上下文ClassLoader可以在创建线程时设置,并允许线程的创建者通过getContextClassLoader方法为在线程中运行的代码提供适当的类加载器,用于加载类和资源。如果存在安全管理器,则会使用其checkPermission方法,传入RuntimePermission的setContextClassLoader权限,以检查是否允许设置上下文ClassLoader。 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @CallerSensitive public  ClassLoader getContextClassLoader ()  {    if  (contextClassLoader == null )         return  null ;     @SuppressWarnings("removal")      SecurityManager  sm  =  System.getSecurityManager();     if  (sm != null ) {         ClassLoader.checkClassLoaderPermission(contextClassLoader,                                                Reflection.getCallerClass());     }     return  contextClassLoader; } public  void  setContextClassLoader (ClassLoader cl)  {    @SuppressWarnings("removal")      SecurityManager  sm  =  System.getSecurityManager();     if  (sm != null ) {         sm.checkPermission(new  RuntimePermission ("setContextClassLoader" ));     }     contextClassLoader = cl; } 
线程 interrupt 
java.lang.Thread#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操作中被阻塞,则通道将被关闭,线程的中断状态将被设置,并且线程将收到一个ClosedByInterruptException异常。
如果此线程在Selector中被阻塞,则线程的中断状态将被设置,并且它将立即从选择操作中返回,可能带有非零值,就像调用了选择器的wakeup方法一样。
如果以上条件都不满足,则将设置此线程的中断状态。
中断一个未启动的线程可能不会产生任何效果。在JDK参考实现中,中断一个未启动的线程仍然记录了中断请求的发出,并通过interrupted和isInterrupted()方法报告它。
java.lang.Thread#interrupted 
测试当前线程是否已被中断。此方法将清除线程的”中断状态”。换句话说,如果连续两次调用此方法,第二次调用将返回false(除非在第一次调用清除了线程的中断状态之后,而第二次调用在检查之前再次中断了当前线程)。
java.lang.Thread#isInterrupted 
测试此线程是否已被中断。此方法不会影响线程的”中断状态”。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public  void  interrupt ()  {    if  (this  != Thread.currentThread()) {         checkAccess();                  synchronized  (blockerLock) {             Interruptible  b  =  blocker;             if  (b != null ) {                 interrupted = true ;                 interrupt0();                   b.interrupt(this );                 return ;             }         }     }     interrupted = true ;          interrupt0(); } public  static  boolean  interrupted ()  {    Thread  t  =  currentThread();     boolean  interrupted  =  t.interrupted;                    if  (interrupted) {         t.interrupted = false ;         clearInterruptEvent();     }     return  interrupted; } public  boolean  isInterrupted ()  {    return  interrupted; } 
测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package  engineer.concurrent.battle.abasic;import  java.util.concurrent.TimeUnit;public  class  ThreadInterruptTest  {    public  static  void  main (String[] args)  {         Thread  t1  =  new  Thread () {             @Override              public  void  run ()  {                 int  i  =  0 ;                 while (i<10 ){                     i++;                     try  {                         TimeUnit.SECONDS.sleep(1 );                     } catch  (InterruptedException e) {                         throw  new  RuntimeException (e);                     }                 }             }         };         t1.start();                  t1.interrupt();         System.out.println("t1 interrupt status "  + t1.isInterrupted());         System.out.println("t1 is interrupted and I can work still. " );                  t1.isInterrupted();         System.out.println("t1 interrupt status "  + t1.isInterrupted());     } } 
线程 join Thread 的 join 方法同样是一个非常重要的方法,与 sleep 一样它也是一个可中断的方法。Thread类通过重载实现了三个函数供多线程开发使用。
java.lang.Thread#join(long) 
等待最多millis毫秒,让此线程死亡。0的超时时间意味着永久等待。此实现使用了一个基于this.isAlive条件的this.wait调用循环。当线程终止时,将调用this.notifyAll方法。建议应用程序不要在Thread实例上使用wait、notify或notifyAll。
java.lang.Thread#join(long, int) 
等待最多 millis 毫秒加上 nanos 纳秒以使此线程死亡。如果两个参数都是 0,那么意味着永远等待。此实现使用一个循环的 this.wait 调用,条件为 this.isAlive。当一个线程终止时,会调用 this.notifyAll 方法。建议应用程序不要在 Thread 实例上使用 wait、notify 或 notifyAll。
等待此线程终止。调用此方法的行为与调用 join(0) 完全相同。
源码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 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);             } while  (isAlive() && (delay = millis -                     TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)) > 0 );         }     } else  if  (millis == 0 ) {         while  (isAlive()) {             wait(0 );         }     } else  {         throw  new  IllegalArgumentException ("timeout value is negative" );     } } public  final  synchronized  void  join (long  millis, int  nanos) throws  InterruptedException {    if  (millis < 0 ) {         throw  new  IllegalArgumentException ("timeout value is negative" );     }     if  (nanos < 0  || nanos > 999999 ) {         throw  new  IllegalArgumentException (                             "nanosecond timeout value out of range" );     }     if  (nanos > 0  && millis < Long.MAX_VALUE) {         millis++;     }     join(millis); } public  final  void  join ()  throws  InterruptedException {    join(0 ); } 
当调用join函数之后主线程和子线程的状态切换如下:
当调用join()方法时,主线程会进入等待状态,直到子线程执行完毕后才会继续执行。此时主线程的状态为WAITING。 
如果调用带参数的join()方法,主线程会在等待一段时间后继续执行,而不必一直阻塞。在这种情况下,主线程的状态为TIMED_WAITING。 
如果子线程已经执行完毕,但是主线程还没有调用join()方法,则子线程的状态为TERMINATED,而主线程的状态为RUNNABLE。 
如果主线程调用join()方法等待子线程完成执行,而子线程抛出了异常,则主线程会收到异常信息并抛出InterruptedException异常。 
 
测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package  engineer.concurrent.battle.abasic;import  java.util.List;import  java.util.concurrent.TimeUnit;import  java.util.stream.IntStream;public  class  ThreadJoinTest  {    public  static  void  main (String[] args)  throws  InterruptedException{         List<Thread> threadList = IntStream.range(1 , 10 ).mapToObj(ThreadJoinTest::create).toList();         threadList.forEach(Thread::start);         for (Thread thread : threadList){             thread.join();         }         IntStream.range(1 , 10 ).forEach((i)-> {             System.out.println("thread "  + Thread.currentThread().getName() + " # " + i );             try  {                 TimeUnit.SECONDS.sleep(1 );             } catch  (InterruptedException e) {                 throw  new  RuntimeException (e);             }         });     }          private  static  Thread create (int  index)  {         return  new  Thread (() -> {             int  i  =  0 ;             while  (i++<10 ) {                 System.out.println("thread "  + Thread.currentThread().getName() + " # " + i );                 try  {                     TimeUnit.SECONDS.sleep(1 );                 } catch  (InterruptedException e) {                     throw  new  RuntimeException (e);                 }             }         });     } } 
通过观察输出结果发现,join之后的线程全部结束后才会执行输出main线程的内容。
1 2 3 4 5 6 7 thread Thread-0  thread Thread-1  thread Thread-7  thread Thread-6  thread main  thread main  thread main  
关闭线程 在JDK 17中,线程停止的情况和函数有以下几种:
自然结束:线程执行完run()方法后,线程会自然结束并进入终止状态。 
线程被中断:可以使用Thread类的interrupt()方法来中断线程。当一个线程调用另一个线程的interrupt()方法时,被调用线程会收到一个中断信号,并且中断状态会被设置为true。中断状态可以通过Thread类的isInterrupted()方法来查询。线程可以在适当的时机检查中断状态,如果中断状态为true,则可以选择安全地终止线程的执行。 
使用标志位停止线程:可以在多线程程序中定义一个标志位,当标志位为true时,线程停止执行。线程可以周期性地检查该标志位,如果标志位为true,则主动结束线程的执行。 
使用Thread类的stop()方法(已废弃):Thread类提供了一个stop()方法,可以立即停止线程的执行。但是这个方法已经被标记为不安全和不推荐使用,因为它可能导致线程在不可预料的位置停止,造成数据不一致或其他问题。 
 
tips native函数 Java中的native关键字用于表示某个方法的实现是由本地代码(C、C++等)提供的。这些本地方法可以直接在Java程序中调用,而无需了解其底层实现。
在Java中,使用native关键字定义本地方法时,不需要提供方法体。例如:
1 public  native  void  myNativeMethod () ;
在上面的示例中,myNativeMethod()被定义为本地方法,并且没有提供方法体。在运行时,Java虚拟机将查找本地方法的实现,如果找不到,则会抛出UnsatisfiedLinkError异常。
要调用本地方法,需要使用native方法的外部实现。这通常涉及到将Java代码与本地代码库进行链接。可以使用Java本机接口(JNI)来实现这一点。
参考 
关于作者 来自一线全栈程序员nine的八年探索与实践,持续迭代中。欢迎关注“雨林寻北”或添加个人卫星codetrend(备注技术)。