3.Thread API 详细介绍

线程sleep

sleep是一个静态方法,其有两个重载方法,其中一个需要传入毫秒数,别一个既需要毫秒数也需要纳秒数。

  • public static void sleep(long millis) throws InterruptedException
  • public static void sleep(long millis, int nanos) throws InterruptedException

sleep 方法会命使当前线程进入指定毫秒数的休眠。但是最终要以系统的定时器和调度器的精度为准。 休眠有一个非常重要的特性, 就是其不会放弃monitor锁的所有权。

每个线程的休眠互不影响,Thread.sleep只会导致当前的线程进入指定时间的休眠。

使用TimeUnit替代Thread.sleep

  • TimeUint.HOURS.sleep(3)
  • TimeUnit.MINUTES.sleep(3)
  • TimeUnit.SECONDS.sleep(3)
  • TimeUnit.MILLISECONDS.sleep(100)

线程yield

yield 方法属于一种启发式的方法, 其会提醒调度器我愿意放弃当前的CPU资源, 如果CPU资源不紧张, 则会忽这种提醒。

调用yield方法会使当前线程从RUNNING状态切换到RUNNABLE状态,一般这个方法不态常用。

public class ThreadYield {
    public static void main(String[] args) {
        IntStream.range(0,2).mapToObj(ThreadYield::create).forEach(Thread::start);
    }

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

书上说上面代码有时候是0先打印出来,有时亿是1先打印出来,测试的时候始终是0,1的顺序,把线程数扩大到100的时候,就会出现不会顺序打印的情况; 可能是因为只有两个线程, 按顺序启动导致的。

打开注释后,运行结果始终是 1, 0

yield和sleep

  • sleep 会导致当前线程暂停指定的时间,没有CPU时间片的消耗
  • yield 只是对CPU调度器的一个提未, 如果CPU调度器没有忽略这个提示, 它会导致线程上下文的切换
  • sleep 会使线程短暂block, 会在给定时间内释放CPU资源
  • yield 会使RUNNING状态的Thread进入RUNNABLE状态(如果调度器没有忽略这个提示的话)
  • sleep 几乎百分百地完成了给定时间的休眠,而yield的提示并不能一定担何
  • 一个线程sleep另一个线程调用interrupt会捕获到中断信号, 而yield则不会。

设置线程的优先级

理论上是优先级比较高的线程会获得优先被CPU调度的机会, 但是事实上往往不会如你所有的愿, 设置线程的优先级同样也是一个hint操作

  • 对于root用户, 它会hint操作系统你想要设置的优先级, 否则它会被忽略
  • 如果CPU比较忙, 设置优先级可能会获得更多的CPU时间片,但是闲优先级的高低几乎不会有任何作用。

所以, 不要在程序设计中企图使用线程优先级绑定某些特定的业务, 或者让业务严重依赖优先级。

public class ThreadPriority {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true) {
                System.out.println("t1");
            }
        });
        t1.setPriority(3);

        Thread t2 = new Thread(() -> {
            while (true) {
                System.out.println("t2");
            }
        });

        t2.setPriority(10);

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

运行上面的程序,会发现t2出现的频率明显要高一些, 不同的情况运行有可能不一样。

再看下设置优先级的源码

    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);
        }
    }

通过源码,可以看出线程的优先级不小于1 ,也不能大于10

如果指定的线程优先级大于线程所在group的优先级,那么将会设置为group的最大优先级。

例:

/**
 * 线程的优先级:
 * 最后结果打印7, 而非10
 */
public class ThreadPriority2 {
    public static void main(String[] args) {

        // 定义一个线程组
        ThreadGroup group = new ThreadGroup("test");

        // 将线程的优先级指定为7
        group.setMaxPriority(7);

        // 定义一个线程, 将该线程加入到group中
        Thread thread = new Thread(group, "test-thread");

        // 尝试将线程优先级设为10
        thread.setPriority(10);

        // 获取线程的优先级
        System.out.println(thread.getPriority());
    }
}

总结

一般情况下,不会对线程设定优先级, 更不会让某些业务严重地依赖线程的优先级。一般定义线程的时候,使用默认的优先级就好了。

默认的线程的优先级和它的父类保持一致, 一般情况下都是5, 因为main线程的优先级就是5, 所以它派生出来的线程都是5

public class ThreadPriority3 {
    public static void main(String[] args) {
        Thread t1 = new Thread();
        System.out.println("t1 priority " + t1.getPriority());

        Thread t2 = new Thread(() -> {
            Thread t3 = new Thread();
            System.out.println("t3 priority " + t3.getPriority());
        });

        t2.setPriority(6);
        t2.start();
        System.out.println("t2 priority "+ t2.getPriority());

        ThreadGroup mainThreadGroup = Thread.currentThread().getThreadGroup();

        System.out.println("main thred group max priority " + mainThreadGroup.getMaxPriority());
    }
}

打印结果:

t1 priority 5
t2 priority 6
main thred group max priority 10
t3 priority 6

获取当前线程Id

  • public long getId()

获取当前线程

  • public static Thread currentThread()

线程interrupt

与线程中断相关的API有如下几个

  • public void interrupt()
  • public static boolean interrupted()
  • public boolean isInterrupted()

如下方法的调用会使当前线程进入阻塞状态,而调用当前线程的interrupt方法,就可以打断阻塞。

  • Object的wait方法
  • Object的wait(long)方法
  • Object的wait(long, int)方法
  • Thread的sleep(long)方法
  • Thread的sleep(long, int)方法
  • Thread的join方法
  • Thread的join(long)方法
  • Thread的join(long, int)
  • InterruptibleChannel的io操作
  • Selector的wakeup方法
  • 其他方法

上述若干方法都会使得当前线程进入阻塞状态, 若另个的一个线程调用被阻塞线程的interrupt方法, 则会打断这种阻塞, 因些这种方法有时候被秒为可中断方法。

注意: 打断一个线程并不等于该线程的生命周期结束, 仅仅是打断了当前线程的阻塞状态。

一旦线程在阻塞的情况下被打断, 都会抛出一个称为InterruptedException的异常, 这个异常就像一个signal(信号)一样通知当前线程被打断了, 下面来看一个例子:

public class ThreadInterrupt {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            try {
                TimeUnit.MINUTES.sleep(1);
            } catch (InterruptedException e) {
                System.out.println("oh, i am be interrupted.");
            }
        });

        thread.start();

        TimeUnit.MILLISECONDS.sleep(2);
        thread.interrupt();
    }
}

运行结果

oh, i am be interrupted.

上面的代码,创建了一个线程,并且想要休眠一分钟,然后在2毫少之后线程调用interrupt方法打断,线程被打断之后抛出InterruptedException异常,然后执行catch块中的内容。

interrupt这个方法到底做了什么样的事情呢? 在一个线程内部存在着名为interrupt flag的标识, 如果一个线程被interrupt, 那么它的flag将被设置, 但是如果当前线程正在执行可中断的方法被阻塞时, 调用interrupt方法将期中断,反而会导致flag被清除。如果一个线程已经是死亡状态, 对其 interrupt会被直接忽略。

isInterrupted

isInterrupted 是Thread的一个成员方法, 它主要判断当前线程是否被中断,该方法仅仅是对interrupt标识的一个判断。

public class ThreadIsInterrupted {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread() {
            @Override
            public void run() {
                while (true) {
                    // do nothing, just empty loop
                }
            }
        };

        thread.start();
        TimeUnit.MILLISECONDS.sleep(2);
        System.out.printf("Thread is interrupted ? %s \n", thread.isInterrupted());
        thread.interrupt();
        System.out.printf("Thread is interrupted ? %s \n", thread.isInterrupted());
    }
}

打印结果

Thread is interrupted ? false 
Thread is interrupted ? true 

interrupted

interrupted是一个静态方法,虽然它也用于判断当前线程是否被中断, 但是它和成员方法isInterrupted的区别是,调用该方法会直接擦除掉线程的interrupt标识。 如果当前线程被打断了, 那么第一次调用interrupted方法会返回true, 并且产即擦除了interrupt标识, 第二次包括以后的调用永远会返回false, 除非在此期间线程又一次地被打断。

public class ThreadInterrupted {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread() {
            @Override
            public void run() {
                while (true) {
                    // 判断是否中断, 并擦除
                    System.out.println(Thread.interrupted());   
                }
            }
        };
        thread.setDaemon(true);
        thread.start();

        TimeUnit.MILLISECONDS.sleep(8);
        // 中断线程
        thread.interrupt();
    }
}

打钱结果在很多 false中夹了一个true, 也就是interrupted方法判断到了其被中断,立即擦除了中断标识, 并且只有这一次返回true. 如果一个线程在没有执行可中断方法之前就被打断, 那么接下来再执行可中断方法,会怎样?

/**
 * 线程提前中断
 * 然后调用可中断方法
 */
public class ThredInterruptedInAdvance {
    public static void main(String[] args) {
        // 判断当前线程是否中断
        System.out.println("Main thread is interrupted? " + Thread.interrupted());

        // 中断线程
        Thread.currentThread().interrupt();

        // 判断当前线程是否已经被中断
        System.out.println("Main thread is interrupted?" + Thread.currentThread().isInterrupted());

        try {
            TimeUnit.MINUTES.sleep(1);
        } catch (InterruptedException e) {
            System.out.println("i will be intrrupted.");
        }

    }
}

运行上面的程序,一旦设置了interrupt标识,接下来调用的可中断方法会立即中断。所以上面代码不会休眠一分钟。

线程join

join方法与sleep一样它也是一个可中断的方法,也就是说,如果有其它线程执行了对当前线程的interrupt操作,它也会捕获到中断信号,并且擦除线程的interrupt标识。

  • public final void join() throws InterruptedException
  • public final synchronized void join(long millis, int nanos) throws InterruptedException
  • public final synchronized void join(long millis) throws InterruptedException

在当前线程A中,调用线程B的join方法, 会使A线程进入等待, 直到线程B结束生命周期, 或者到达给定时间,A才会继续执行。

public class ThreadJoin {
    public static void main(String[] args) throws InterruptedException {
        List<Thread> threads = IntStream.range(1, 3).mapToObj(ThreadJoin::create).collect(Collectors.toList());
        threads.forEach(Thread::start);

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

        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "#" + i);
            shortSleep();
        }
    }

    private static Thread create(int seq) {
        return new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "#" + i);
                shortSleep();
            }
        }, String.valueOf(seq));
    }

    private static void shortSleep() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

如何关闭一个线程

  1. 线程结束生命周期正常结束

  2. 捕获中断信号关闭线程

public class InterruptThreadExit {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread() {
            @Override
            public void run() {
                System.out.println("i will start work");
                while (!isInterrupted()) {
                    // working
                }
                System.out.println("i will be exiting");
            }
        };
        t.start();
        TimeUnit.MINUTES.sleep(1);
        System.out.println("System will be shutdown");
        t.interrupt();
    }
}

上面的代是通过检查线程的interrupt的标识来决定是否退出的, 如果在线程中执行某个可中断方法,则可以通过捕获中断信号来决定是否退出。

@Override
public void run() {
    System.out.println("I will start work");
    for (;;) {
        try {
            TimeUnit.MILLISECONDS.sleep(1);
        } catch (InterruptedException e) {
            break;
        }
    }
    System.out.println("I will be exiting");
}
  1. 使用volatile开关控制 由于线程的interrupt标识很有可能被擦除, 或者逻辑单远中不会调用任何可中断方法, 所以使用volatile修饰的开关flag关闭线程也是一种常用的做法, 具体如下:
public class FlagThreadExit {
    static class MyTask extends Thread {
        private volatile boolean closed = false;

        @Override
        public void run() {
            System.out.println("I will start work");
            while (!closed && !isInterrupted()) {
                // working
            }
            System.out.println("I will be exiting");
        }

        public void close() {
            this.closed = true;
            this.interrupt();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyTask t = new MyTask();
        t.start();
        TimeUnit.MINUTES.sleep(1);
        System.out.println("System will be shutdown");
        t.close();
    }
}