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

打印 上一主题 下一主题

主题 849|帖子 849|积分 2562

弁言

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

线程 sleep


  • 使当前正在执行的线程停息(挂起)指定的毫秒数。但受系统计时器和调度程序的精度和准确性限制。
  • 线程不会失去任何monitor(监视器)的全部权。
  • 每个线程的休眠互不影响,Thread.sleep 只会导致当火线程进入指定时间的休眠。
  1.     public static native void sleep(long millis) throws InterruptedException;
  2.     public static void sleep(long millis, int nanos)
  3.     throws InterruptedException {
  4.         if (millis < 0) {
  5.             throw new IllegalArgumentException("timeout value is negative");
  6.         }
  7.         if (nanos < 0 || nanos > 999999) {
  8.             throw new IllegalArgumentException(
  9.                                 "nanosecond timeout value out of range");
  10.         }
  11.         if (nanos > 0 && millis < Long.MAX_VALUE) {
  12.             millis++;
  13.         }
  14.         sleep(millis);
  15.     }
复制代码
通过测试发现 Thread.sleep 之间互不影响。代码如下:
  1. /**
  2. * 每个线程的休眠互不影响,`Thread.sleep` 只会导致当前线程进入指定时间的休眠。
  3. */
  4. public class ThreadSleepTest {
  5.     public static void main(String[] args) throws Exception{
  6.         Thread thread1 = new Thread(()->{
  7.             int i = 0;
  8.             while(i<10){
  9.                 System.out.println("thread demo start "+i);
  10.                 i++;
  11.                 try {
  12.                     Thread.sleep(1000);
  13.                 } catch (InterruptedException e) {
  14.                     throw new RuntimeException(e);
  15.                 }
  16.             }
  17.         });
  18.         thread1.start();
  19.         System.out.println("thread main start ");
  20.         Thread.sleep(2000);
  21.         System.out.println("thread main end ");
  22.     }
  23. }
复制代码
线程 yield

Thread.yield()是一个提示,用于告诉调度程序当火线程愿意放弃使用处置惩罚器。调度程序可以选择忽略这个提示。Yield是一种试图改善线程之间相对进程的开导式方法,否则它们会太过利用CPU。它的使用应该与详细的分析和基准测试联合起来,以确保它确实产生了预期的效果。
这种方法适用场景很少。它有助于调试或测试,以资助重现由于竞态条件而引起的错误。在筹划并发控制布局时,例如java.util.concurrent.locks包中的布局,它也大概有效。
调用Thread.yield()函数会将当火线程从RUNNING状态切换到RUNNABLE状态。
  1. thread main start
  2. thread demo start 0
  3. thread demo start 1
  4. thread main end
  5. thread demo start 2
  6. thread demo start 3
复制代码
测试代码如下,在cpu资源不紧张的环境下输出仍旧是乱序的。
  1. package engineer.concurrent.battle.abasic;
  2. import java.util.concurrent.TimeUnit;
  3. /**
  4. * TimeUnit工具类替代Thread.sleep方法。
  5. * @author r0ad
  6. * @since 1.0
  7. */
  8. public class ThreadSleepTimeUnitTest {
  9.     public static void main(String[] args) throws Exception{
  10.         System.out.println("thread main start ");
  11.         TimeUnit.SECONDS.sleep(1);
  12.         TimeUnit.MILLISECONDS.sleep(500);
  13.         TimeUnit.MINUTES.sleep(1);
  14.         System.out.println("thread main end ");
  15.     }
  16. }
  17. /**
  18. * java.util.concurrent.TimeUnit#sleep 源码,底层实现也是Thread.sleep。
  19. * Performs a {@link Thread#sleep(long, int) Thread.sleep} using
  20. * this time unit.
  21. * This is a convenience method that converts time arguments into the
  22. * form required by the {@code Thread.sleep} method.
  23. *
  24. * @param timeout the minimum time to sleep. If less than
  25. * or equal to zero, do not sleep at all.
  26. * @throws InterruptedException if interrupted while sleeping
  27. */
  28. public void sleep(long timeout) throws InterruptedException {
  29.     if (timeout > 0) {
  30.         long ms = toMillis(timeout);
  31.         int ns = excessNanos(timeout, ms);
  32.         Thread.sleep(ms, ns);
  33.     }
  34. }
复制代码
输出结果:
  1.     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和线程所属线程组答应的最大优先级中较小的值。
  1. package engineer.concurrent.battle.abasic;
  2. import java.util.stream.IntStream;
  3. /**
  4. * ThreadYield测试
  5. * @author r0ad
  6. * @since 1.0
  7. */
  8. public class ThreadYieldTest {
  9.     public static void main(String[] args) throws Exception{
  10.         System.out.println("thread main start ");
  11.         IntStream.range(0, 2).mapToObj(ThreadYieldTest::create).forEach(Thread::start);
  12.         System.out.println("thread main end ");
  13.     }
  14.     private static Thread create(int i) {
  15.         return new Thread(() -> {
  16.             if(i == 0 ){
  17.                 Thread.yield();
  18.             }
  19.             System.out.println("thread " + i + " start ");
  20.         });
  21.     }
  22. }
复制代码
进程有进程的优先级,线程同样也有优先级,理论上是优先级比较高的线程会获取优先被 CPU 调度的机会,但是事实上设置线程的优先级同样也是一个 hint 操作,具体如下。

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

返回此线程的标识符。线程ID是一个正的long数字,在创建此线程时生成。线程ID是唯一的,并在其生命周期内保持不变。当一个线程终止时,该线程ID大概会被重新使用。
  1.     /**
  2.      * `java.lang.Thread#setPriority` 修改线程的优先级源码
  3.      */
  4.     public final void setPriority(int newPriority) {
  5.         ThreadGroup g;
  6.         // 调用此线程的`checkAccess`方法,不带任何参数。这可能会导致抛出一个`SecurityException`异常。
  7.         checkAccess();
  8.         if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
  9.             throw new IllegalArgumentException();
  10.         }
  11.         if((g = getThreadGroup()) != null) {
  12.             // 线程的优先级被设置为指定的`newPriority`和线程所属线程组允许的最大优先级中较小的值。
  13.             if (newPriority > g.getMaxPriority()) {
  14.                 newPriority = g.getMaxPriority();
  15.             }
  16.             setPriority0(priority = newPriority);
  17.         }
  18.     }
  19.     /**
  20.      * `java.lang.Thread#getPriority` 获取线程的优先级
  21.      */
  22.     public final int getPriority() {
  23.         // 返回Thread的priority属性
  24.         return priority;
  25.     }
  26.     /* 原生优先级设置方法 */
  27.     private native void setPriority0(int newPriority);
复制代码
获取当火线程

java.lang.Thread#currentThread 方法被大多数框架使用,像是SpringMVC、MyBatis这些。调用该函数会返回当前正在执行的线程对象。
  1. package engineer.concurrent.battle.abasic;
  2. /**
  3. * ThreadPriorityTest测试
  4. * @author r0ad
  5. * @since 1.0
  6. */
  7. public class ThreadPriorityTest {
  8.     public static void main(String[] args) throws Exception{
  9.         Thread t1 = ThreadPriorityTest.create("t1");
  10.         t1.setPriority(1);
  11.         Thread t2 = ThreadPriorityTest.create("t2");
  12.         t2.setPriority(10);
  13.         t1.start();
  14.         t2.start();
  15.     }
  16.     private static Thread create(String name) {
  17.         return new Thread(() -> {
  18.             while (true) {
  19.                 System.out.println("thread " + name );
  20.             }
  21.         });
  22.     }
  23. }
复制代码
测试代码如下:
  1.     public long getId() {
  2.         return tid;
  3.     }
复制代码
设置线程上下文类加载器


  • java.lang.Thread#getContextClassLoader 返回该线程的上下文ClassLoader。上下文ClassLoader由创建线程的对象提供,用于在此线程中运行的代码在加载类和资源时使用。如果未设置(通过setContextClassLoader()方法),则默以为父线程的ClassLoader上下文。原始线程的上下文ClassLoader通常设置为用于加载应用程序的类加载器。
  • java.lang.Thread#setContextClassLoader 设置此线程的上下文ClassLoader。上下文ClassLoader可以在创建线程时设置,并答应线程的创建者通过getContextClassLoader方法为在线程中运行的代码提供适当的类加载器,用于加载类和资源。如果存在安全管理器,则会使用其checkPermission方法,传入RuntimePermission的setContextClassLoader权限,以查抄是否答应设置上下文ClassLoader。
  1.     @IntrinsicCandidate
  2.     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
测试此线程是否已被停止。此方法不会影响线程的"停止状态"。
  1. package engineer.concurrent.battle.abasic;
  2. /**
  3. * ThreadCurrentTest测试
  4. * @author r0ad
  5. * @since 1.0
  6. */
  7. public class ThreadCurrentTest {
  8.     public static void main(String[] args) throws Exception{
  9.         Thread t1 = new Thread() {
  10.             @Override
  11.             public void run() {
  12.                 System.out.println(this == Thread.currentThread());
  13.             }
  14.         };
  15.         t1.start();
  16.         System.out.println(Thread.currentThread().getName());
  17.     }
  18. }
复制代码
测试代码如下:
  1.     @CallerSensitive
  2.     public ClassLoader getContextClassLoader() {
  3.         if (contextClassLoader == null)
  4.             return null;
  5.         @SuppressWarnings("removal")
  6.         SecurityManager sm = System.getSecurityManager();
  7.         if (sm != null) {
  8.             ClassLoader.checkClassLoaderPermission(contextClassLoader,
  9.                                                    Reflection.getCallerClass());
  10.         }
  11.         return contextClassLoader;
  12.     }
  13.     public void setContextClassLoader(ClassLoader cl) {
  14.         @SuppressWarnings("removal")
  15.         SecurityManager sm = System.getSecurityManager();
  16.         if (sm != null) {
  17.             sm.checkPermission(new RuntimePermission("setContextClassLoader"));
  18.         }
  19.         contextClassLoader = cl;
  20.     }
复制代码
当调用join函数之后主线程和子线程的状态切换如下:

  • 当调用join()方法时,主线程会进入等待状态,直到子线程执行完毕后才会继续执行。此时主线程的状态为WAITING。
  • 如果调用带参数的join()方法,主线程会在等待一段时间后继续执行,而不必一直壅闭。在这种环境下,主线程的状态为TIMED_WAITING。
  • 如果子线程已经执行完毕,但是主线程还没有调用join()方法,则子线程的状态为TERMINATED,而主线程的状态为RUNNABLE。
  • 如果主线程调用join()方法等待子线程完成执行,而子线程抛出了非常,则主线程会收到非常信息并抛出InterruptedException非常。
测试代码如下:
[code]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++

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

大连全瓷种植牙齿制作中心

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表