关于java:JAVA进阶系列-并发编程-第5篇-Thread-API

在上一篇中介绍了 Thread 类的构造方法,可是光有构造方法也不够,咱们还得再学习多一些该类罕用的 API 才行,这样能力对该类有更粗浅的理解,同时也能让咱们有更多的抉择。

Thread类提供的API有几十个,因为篇幅问题,本篇文章仅抉择几个有代表性的来进行解说。残余的API小伙伴们感兴趣的能够通过源码进行查看,也能够给我留言,咱们独特探讨独特学习。

指标

  1. currentThread
  2. setPriority
  3. yield
  4. sleep
  5. interrupt
  6. interrupted
  7. join

内容

1. currentThread

该办法用于返回以后执行线程的援用,咱们能够在代码块中通过它来获取以后的线程对象,尽管看起来很简略,然而应用十分宽泛,在后续的内容中都会大量应用到该办法。

办法:

public static native Thread currentThread();

代码:

/**
 * 这个例子咱们能够看到 Thread.currentThread() 这里拿到的都是各自的执行线程援用对象。
 */
public class CurrentThreadDemo {
    public static void main(String[] args) {
        // 打印后果为:true
        Thread t = new Thread(() -> {
            System.out.println("t".equals(Thread.currentThread().getName()));
        }, "t");
        t.start();
        // 打印后果为:true
        System.out.println("main".equals(Thread.currentThread().getName()));
    }
}

2. setPriority

过程有过程的优先级,线程同样也有优先级,实践上是优先级比拟高的线程会优先被CPU进行调度,但事实上往往并不会如你所愿。

如果CPU比较忙,设置优先级可能会取得更多的CPU工夫片,然而在CPU闲暇的状况下,设置优先级简直不会有任何作用。所以,咱们不要试图在程序设计中应用优先级来绑定某些业务或者让业务依赖于优先级,这产生的后果可能会与你所冀望的后果不统一。

办法:

public final void setPriority(int newPriority);    // 设置线程优先级
public final int getPriority();    // 获取线程优先级

案例:

/**
 * t1 线程的优先级比 t2 线程的低,失常来说应该统计数量会比 t2 线程的少
 * 然而我这里随机测试统计到的次数为:
 *    t1: 59573
 *    t2: 34321
 * 不同的CPU资源状况会有不同的运行后果,小伙伴们能够多测试几次看看
 */
public class PriorityDemo {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true) {
                System.out.println("t1");
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            while (true) {
                System.out.println("t2");
            }
        }, "t2");
        // 最小值为:1,两头值为:5,最大值为:10
        t1.setPriority(9);
        t2.setPriority(10);

        t1.start();
        t2.start();
    }
}

3. yield

yield办法属于一种启发式的办法,它给调度程序的提醒是以后线程违心放弃对处理器的以后应用。调度程序能够随便疏忽此提醒。应将其应用与具体的性能剖析和基准测试联合起来,以确保它实际上具备所需的成果。

此办法很少被应用。对于调试或测试目标,它可能很有用,因为它可能有助于重现因为竞争条件而产生的谬误。

办法:

public static native void yield();

案例:

/**
 * 将正文局部放开的话能够看到控制台输入的后果有时候是 0 在后面,有时候是 1 在后面
 * 而将该局部正文之后,你会发现一只都是 0 在后面
 */
public class YieldDemo {
    public static void main(String[] args) {
        IntStream.range(0, 2).mapToObj(YieldDemo::test).forEach(Thread::start);
    }

    private static Thread test(int index){
        return new Thread(() -> {
            // if (index == 0){
            //     Thread.yield();
            // }
            System.out.println(index);
        });
    }
}

4. sleep

sleep是一个静态方法,依据零碎计时器和调度程序的精度和准确性,使以后正在执行的线程进入休眠状态(临时进行执行)达指定的毫秒数。该线程不会失去任何监视器的所有权(例如monitor锁,对于monitor锁在后续的文章中会进行具体解说)。

办法:

public static native void sleep(long millis);    // 休眠的毫秒数
public static void sleep(long millis, int nanos);    // 休眠的毫秒数与纳秒数

案例:

/**
 * 在这个例子中,咱们别离在自定义线程喝主线程中进行了休眠,每个线程的休眠互不影响
 * Thread.sleep() 只会导致以后线程休眠指定的工夫
 */
public class SleepDemo {
    public static void main(String[] args) {
        new Thread(() -> {
            long startTime = System.currentTimeMillis();
            sleep(2000);
            System.out.printf("%s线程耗时:%d%s", Thread.currentThread().getName(), System.currentTimeMillis() - startTime, "ms");
            System.out.println("");
        }, "t").start();

        long startTime = System.currentTimeMillis();
        sleep(3000);
        System.out.printf("%s线程耗时:%d%s", Thread.currentThread().getName(), System.currentTimeMillis() - startTime, "ms");
    }

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

yield 和 sleep 的区别:

  • yield 只是对 CPU 调度器的一个提醒,如果失效了,会导致线程上下文的切换;
  • sleep 会导致以后线程暂停指定的工夫,没有 CPU 工夫片的耗费;
  • yield 会使线程由 RUNNING 状态进入 RUNNABLE 状态;
  • sleep 会使线程短暂 block,之后在给定的工夫内开释 CPU 资源;
  • yield 不能保障肯定失效,而 sleep 简直百分百的依照指定工夫进行休眠(最终休眠工夫要以零碎的定时器和调度器的精度为准)
  • yield 无奈捕捉中断信号,而 sleep 被另一个线程打断则能捕捉到中断信号

5. interrupt

线程interrupt是一个十分重要的API,也是常常应用的办法,如果在调用:

  1. Object类的 wait(),wait(long)或wait(long,int)办法或join(),join(long),join(long,int)的办法时阻塞了此线程,此类的sleep(long)或sleep(long,int)办法,则其中断状态将被革除,并将收到InterruptedException
  2. InterruptibleChannel 的 I/O 操作,则该通道将被敞开,该线程的中断状态将被设置,并且该线程将收到java.nio.channels.ClosedByInterruptException
  3. Selector 的 wakeup 办法,则将设置该线程的中断状态,并且它将立刻从抉择操作中返回(可能具备非零值),就像调用选择器的唤醒办法一样。

与之相干的API还有几个。

办法:

public void interrupt();    // 中断阻塞
public static boolean interrupted();    // 判断以后线程是否被中断,该办法会间接擦除掉线程的标识
public boolean isInterrupted();    // 判断以后线程是否被中断,仅做判断不影响标识
// interrupted 和 isInterrupted 办法都是调用本地办法 isInterrupted() 来实现,该办法中的参数 ClearInterrupted 次要用来管制是否擦除线程 interrupt 的标识,该标识被擦除后,后续的所有判断都将会是 false
private native boolean isInterrupted(boolean ClearInterrupted);

案例:

/**
 * 新建一个线程 t,休眠 1分钟
 * 主线程休眠 2秒钟之后,对线程 t进行打断,控制台输入:interrupt,程序完结
 */
public class InterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            try {
                Thread.sleep(60 * 1000);
            } catch (InterruptedException e) {
                System.out.println("interrupt");
            }
        }, "t");
        t.start();

        Thread.sleep(2000);
        t.interrupt();
    }
}

在线程外部中存在一个名为 interrupt flag 的标识,如果一个线程被 interrupt,那么它的flag将被设置,在源码中咱们也能够看到有对应的形容。

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

    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
            interrupt0();           // Just to set the interrupt flag
            b.interrupt(this);
            return;
        }
    }
    interrupt0();
}

然而如果以后线程正在执行可中断办法被阻塞时,调用interrupt办法将其中断,反而会导致flag被分明,对于这点在前面的文章中还会具体介绍。

7. join

Thread 的 join 办法同样也是一个十分重要的办法,应用它的个性能够实现很多比拟强的性能,在 Thread 类中给咱们提供了三个不同的 办法,具体如下。

办法:

public final void join();    // 永恒期待该线程生命周期完结
public final synchronized void join(long millis);    // 设置最长期待毫秒值,为 0 则永恒期待
public final synchronized void join(long millis, int nanos); // 设置最长期待毫秒值与纳秒值,为 0 则永恒期待

案例:

/**
 * 创立两个线程 1 和 2 并别离启动,在控制台进行输入。
 * 同时main线程调用这两个线程的办法,这时你会发现线程 1 和 2 会交替的输入直到两个线程都运行结束
 * 此时main线程才开始循环输入,如果将 join 办法正文掉,则三个线程会同时进行输入
 */
public class JoinDemo {
    public static void main(String[] args) throws InterruptedException {
        List<Thread> list = IntStream.range(1, 3).mapToObj(JoinDemo::getThread).collect(Collectors.toList());

        list.forEach(Thread::start);

        for (Thread thread : list) {
            thread.join();
        }

        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "-" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private static Thread getThread(int name){
        return new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "-" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, String.valueOf(name));
    }
}

总结

在本篇文章中,咱们学习了 Thread 的一些较常见的 API,Thread 的 API 是把握高并发编程的根底,十分有必要熟练掌握!

明天的文章到这里就完结了,小伙伴们有什么倡议或者意见请分割我改良哦,你们的反对是我最大的能源!!!
本文由博客群发一文多发等经营工具平台 OpenWrite 公布

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理