多线程——线程的常用方法

sleep

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

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

sleep方法会使当前线程进入指定毫秒的休眠,暂停执行,虽然给定了休眠的时间,但是最终要以系统的定时器和调度器的精度为准,
sleep的休眠有一个很重要的特性,就是不会放弃monitor锁的所有权

JDK 1.5以后,引入了一个枚举TimeUnit 其对sleep方法进行了很好的封装

    public void sleep(long timeout) throws InterruptedException {
        if (timeout > 0) {
            long ms = toMillis(timeout);
            int ns = excessNanos(timeout, ms);
            Thread.sleep(ms, ns);
        }
    }

使用实例

      TimeUnit.SECONDS.sleep(10);
      TimeUnit.MILLISECONDS.sleep(200);
      TimeUnit.HOURS.sleep(1);

yield

yield方法属于一种启发式的方法,其会提醒调度器我愿意放弃当前的CPU资源,如果CPU资源不紧张,则会忽略这个提醒。即yield只是一个提示,CPU调度器并不会担保每次都能满足yield的提示。

yield VS sleep

  1. 是否导致线程上下文的切换
    sleep会导致当前线程暂停指定的时间,没有CPU的时间片消耗

yield只会CPU调度器发一个提示,如果CPU调度器不紧张则会忽略这个提示,如果没有忽略这个提示,会导致线程上下文的切换

  1. 导致的线程状态切换
    sleep 会导致线程短暂进入Block, 会在给定的时间内释放CPU资源
    yield 会是Running状态的线程进入Runnable的状态(如果CPU调度器没有忽略这个提示的话)

  2. 时间上
    sleep 几乎百分百完成给定时间的休眠
    yield 的提示并不能一定担保

  3. 中断信号
    一个线程sleep 另一个线程调用interrupt 会捕获到中断信号
    yield则不会

priority

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

  • 对于root用户,它会hint操作系统你想要的设置的优先级别,否则它会被忽略
  • 如果CPU太忙,设置优先级可能会获得更多CPU时间片,但是闲时优先级的高低几乎不会有任何作用。
    所以,不要在程序设计中企图用线程的优先级来绑定某些特性业务,或者让业务严重依赖线程的优先级。

线程的默认优先级

    /**
     * The minimum priority that a thread can have.
     */
    public static final int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public static final int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public static final int MAX_PRIORITY = 10;

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

上面源码分析得出,线程的优先级不能大于10 和小于1 , 如果指定线程的优先级大于线程所在组的最大优先级,则指定优先级将会失效,取而代之的是当前线程所在组的最大线程优先级。如果小于所在组的最大线程优先级, 则设置为需要设置的线程优先级。

线程的上下文类加载器

线程的上下文类加载器,就是这个线程是有那个类加载器加载的,如果在没有修改线程上下文类加载器的情况下,则保持与父线程同样的类加载器
如果设置该线程的类加载器,这个方法则可以打破Java类加载器的父委托机制,有时也称为Java类加载器的后门。

interrupt

public void interrupt();
/**
此方法是一个静态方法,虽然也用于对当前线程是否被中断,但是和成员方法isInterrupted方法的区别,
就是调用该方法会直接擦除掉线程的interrupt标识,

注意:如果当前线程被打断了,那么第一次调用interrupted方法会返回true, 
并且立即擦除interrupt标识,  第二次包括以后调用永远都会返回false, 
除非再次期间线程又一次被打断
*/
public static boolean interrupted();
/**
Thread的成员方法,主要是判断当前线程是否被中断,该方法
仅仅是对interrupt标识的一个判断,并不会影响标识发生任何改变
*/
public boolean isInterrupted();

调用如下方法会使当前线程进入阻塞状态

  1. Object 的wait()
  2. Object 的wait(long)
  3. Object 的wait(long, int)
  4. Thread 的sleep(long)
  5. Thread 的sleep(long, int)
  6. Thread 的join()
  7. Thread 的join(long)
  8. Thread 的join(long, int)
  9. InterruptibleChannel的IO操作
  10. Selector的wakeup方法
  11. 其他

而调用当前线程的Interrupt方法,则就可以打断阻塞, 因此此方法也称为可中断方法。
注意:
打断一个线程不等于该线程的生命周期结束,仅仅是打断了当前线程的阻塞状态。
如果一个线程执行可中断方法被阻塞时,调用interrupt方法将其中断,会导致其中断标识被清除。
如果一个线程已经试死亡状态,那么尝试对其调用interrupt方法会直接被忽略

join

与sleep一样, 它也是一个可中断方法。也即如果有其他线程执行了对当前线程的interrupt操作,它也会捕捉到中断信号,并且擦除线程的interrupt标识。
join某个线程A , 会使当前线程B(比如main线程)进入等待,知道线程A结束生命周期,或者到达指定的时间,那么在此期间线程B是Blocked的

实例:
比如需要你做一个接口请求,这个请求的返回需要调用多个接口的返回进行组合,这里用join来做并行多个请求,等都返回后再进行组合,当然也可用JDK自带的CountDownLatch 或者CyclicBarrier等,这里我们用join来实现一把。

public class ClientRequestTask extends Thread {

    public static void main(String[] args) {
        List tasks = IntStream.range(1, 3).boxed()
                .map(url -> new ClientRequestTask("url:" + url)).collect(Collectors.toList());

        tasks.forEach(Thread::start);

        tasks.forEach(task -> {
            try {
                task.join();
            } catch (InterruptedException e) {

            }
        });

        List list = new ArrayList<>();
        tasks.stream().map(ClientRequestTask::getResults).forEach(list::addAll);
        System.out.println(list);
    }

    private String url;

    private final List results = new ArrayList<>();

    public ClientRequestTask(String url) {
        this.url = url;
    }


    public List getResults() {
        return results;
    }

    @Override
    public void run() {
        try {
            System.out.println(url + " start request data");
            int i = ThreadLocalRandom.current().nextInt(10);
            TimeUnit.SECONDS.sleep(ThreadLocalRandom.current().nextInt(10));
            results.add(url + "---------" + i);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

如何关闭一个线程

  1. JDK有一个Deprecated的方法stop, 但是该方法关闭线程时不会释放掉monitor锁,所以官方已经不推荐使用此方法了。
  2. 线程结束生命周期
  3. 捕获中断信号关闭线程
  4. 使用volatile开关控制
  5. 异常退出

你可能感兴趣的:(多线程——线程的常用方法)