大连全瓷种植牙齿制作中心 发表于 2024-5-13 09:16:20

并发编程Thread的常用API有哪些?

弁言

在JDK17(或以上版本)中,Thread类提供了一组常用的API,用于管理线程的创建、启动、停息、恢复和销毁等操作。本文从api、源码、编程示例等方面详细说明Thread常用函数的使用和注意事项。
https://img2024.cnblogs.com/blog/1684206/202403/1684206-20240308092448205-1219412479.png
线程 sleep


[*]使当前正在执行的线程停息(挂起)指定的毫秒数。但受系统计时器和调度程序的精度和准确性限制。
[*]线程不会失去任何monitor(监视器)的全部权。
[*]每个线程的休眠互不影响,Thread.sleep 只会导致当火线程进入指定时间的休眠。
    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 之间互不影响。代码如下:
/**
* 每个线程的休眠互不影响,`Thread.sleep` 只会导致当前线程进入指定时间的休眠。
*/
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 ");
    }
}线程 yield

Thread.yield()是一个提示,用于告诉调度程序当火线程愿意放弃使用处置惩罚器。调度程序可以选择忽略这个提示。Yield是一种试图改善线程之间相对进程的开导式方法,否则它们会太过利用CPU。它的使用应该与详细的分析和基准测试联合起来,以确保它确实产生了预期的效果。
这种方法适用场景很少。它有助于调试或测试,以资助重现由于竞态条件而引起的错误。在筹划并发控制布局时,例如java.util.concurrent.locks包中的布局,它也大概有效。
调用Thread.yield()函数会将当火线程从RUNNING状态切换到RUNNABLE状态。
thread main start
thread demo start 0
thread demo start 1
thread main end
thread demo start 2
thread demo start 3测试代码如下,在cpu资源不紧张的环境下输出仍旧是乱序的。
package engineer.concurrent.battle.abasic;

import java.util.concurrent.TimeUnit;

/**
* TimeUnit工具类替代Thread.sleep方法。
* @author r0ad
* @since 1.0
*/
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 ");
    }
}

/**
* java.util.concurrent.TimeUnit#sleep 源码,底层实现也是Thread.sleep。
* Performs a {@link Thread#sleep(long, int) Thread.sleep} using
* this time unit.
* This is a convenience method that converts time arguments into the
* form required by the {@code Thread.sleep} method.
*
* @param timeout the minimum time to sleep. If less than
* or equal to zero, do not sleep at all.
* @throws InterruptedException if interrupted while sleeping
*/
public void sleep(long timeout) throws InterruptedException {
    if (timeout > 0) {
      long ms = toMillis(timeout);
      int ns = excessNanos(timeout, ms);
      Thread.sleep(ms, ns);
    }
}输出结果:
    public static native void yield();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和线程所属线程组答应的最大优先级中较小的值。
package engineer.concurrent.battle.abasic;

import java.util.stream.IntStream;

/**
* ThreadYield测试
* @author r0ad
* @since 1.0
*/
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 ");
      });
    }
}进程有进程的优先级,线程同样也有优先级,理论上是优先级比较高的线程会获取优先被 CPU 调度的机会,但是事实上设置线程的优先级同样也是一个 hint 操作,具体如下。

[*]对于 root 用户,它会 hint 操作系统你想要设置的优先级别,否则它会被忽略。
[*]如果 CPU 比较忙,设置优先级大概会获得更多的 CPU 时间片,但是闲时优先级的高低几乎不会有任何作用。
所以不要使用线程的优先级进行某些特定业务的绑定,业务执行的次序应该还是要使用同步执行方法来保证。
测试例子如下,线程之间会交替输出:
thread main start
thread main end
thread 0 start
thread 1 start 获取线程ID

返回此线程的标识符。线程ID是一个正的long数字,在创建此线程时生成。线程ID是唯一的,并在其生命周期内保持不变。当一个线程终止时,该线程ID大概会被重新使用。
    /**
   * `java.lang.Thread#setPriority` 修改线程的优先级源码
   */
    public final void setPriority(int newPriority) {
      ThreadGroup g;
      // 调用此线程的`checkAccess`方法,不带任何参数。这可能会导致抛出一个`SecurityException`异常。
      checkAccess();
      if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
            throw new IllegalArgumentException();
      }
      if((g = getThreadGroup()) != null) {
            // 线程的优先级被设置为指定的`newPriority`和线程所属线程组允许的最大优先级中较小的值。
            if (newPriority > g.getMaxPriority()) {
                newPriority = g.getMaxPriority();
            }
            setPriority0(priority = newPriority);
      }
    }

    /**
   * `java.lang.Thread#getPriority` 获取线程的优先级
   */
    public final int getPriority() {
      // 返回Thread的priority属性
      return priority;
    }
    /* 原生优先级设置方法 */
    private native void setPriority0(int newPriority);获取当火线程

java.lang.Thread#currentThread 方法被大多数框架使用,像是SpringMVC、MyBatis这些。调用该函数会返回当前正在执行的线程对象。
package engineer.concurrent.battle.abasic;

/**
* ThreadPriorityTest测试
* @author r0ad
* @since 1.0
*/
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 );
            }
      });
    }
}测试代码如下:
    public long getId() {
      return tid;
    }设置线程上下文类加载器


[*]java.lang.Thread#getContextClassLoader 返回该线程的上下文ClassLoader。上下文ClassLoader由创建线程的对象提供,用于在此线程中运行的代码在加载类和资源时使用。如果未设置(通过setContextClassLoader()方法),则默以为父线程的ClassLoader上下文。原始线程的上下文ClassLoader通常设置为用于加载应用程序的类加载器。
[*]java.lang.Thread#setContextClassLoader 设置此线程的上下文ClassLoader。上下文ClassLoader可以在创建线程时设置,并答应线程的创建者通过getContextClassLoader方法为在线程中运行的代码提供适当的类加载器,用于加载类和资源。如果存在安全管理器,则会使用其checkPermission方法,传入RuntimePermission的setContextClassLoader权限,以查抄是否答应设置上下文ClassLoader。
    @IntrinsicCandidate
    public static native Thread currentThread();线程 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
测试此线程是否已被停止。此方法不会影响线程的"停止状态"。
package engineer.concurrent.battle.abasic;

/**
* ThreadCurrentTest测试
* @author r0ad
* @since 1.0
*/
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());
    }
}测试代码如下:
    @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;
    }当调用join函数之后主线程和子线程的状态切换如下:

[*]当调用join()方法时,主线程会进入等待状态,直到子线程执行完毕后才会继续执行。此时主线程的状态为WAITING。
[*]如果调用带参数的join()方法,主线程会在等待一段时间后继续执行,而不必一直壅闭。在这种环境下,主线程的状态为TIMED_WAITING。
[*]如果子线程已经执行完毕,但是主线程还没有调用join()方法,则子线程的状态为TERMINATED,而主线程的状态为RUNNABLE。
[*]如果主线程调用join()方法等待子线程完成执行,而子线程抛出了非常,则主线程会收到非常信息并抛出InterruptedException非常。
测试代码如下:
package engineer.concurrent.battle.abasic;import java.util.List;import java.util.concurrent.TimeUnit;import java.util.stream.IntStream;/** * ThreadJoinTest * @author r0ad * @since 1.0 */public class ThreadJoinTest {    public static void main(String[] args) throws InterruptedException{      List 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);            }      });    }    /**   * 线程创建函数   * @param index   * @return   */    private static Thread create(int index) {      return new Thread(() -> {            int i = 0;            while (i++
页: [1]
查看完整版本: 并发编程Thread的常用API有哪些?