Java并发编程系列---Thread API详解

Thread API详解

一、线程sleep

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 >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }

        sleep(millis);
    }

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

//当前线程休眠1s
Thread.sleep(1000);

1.1 用TimeUtil代替Thread.sleep

在JDK1.5以后,JDK引入了一个枚举TimeUnit,其对sleep方法提供了很好的封装,使用它可以省去时间单位的换算步骤,比如线程想休眠1小时23分12秒44毫秒,使用TimeUnit来实现就非常的简便优雅了:

        TimeUnit.HOURS.sleep(1);
        TimeUnit.MINUTES.sleep(23);
        TimeUnit.SECONDS.sleep(12);
        TimeUnit.MILLISECONDS.sleep(44);

同样的时间表达,TimeUnit显然清晰很多,所以,在使用Thread.sleep的地方,完全使用TimeUnit来代替,因为sleep能做的事,TimeUnit全部都能完成,并且功能更加的强大。

二、线程yield

yield方法属于一种启发式的方法,其会提醒调度器我愿意放弃当前的CPU资源,如果CPU的资源不紧张,则会忽略这种提醒。
调用yield方法会使当前线程从RUNNING状态切换到RUNNABLE状态,一般这个方法不太常用。使用方法:

Thread.yield();

注意:yield只是一个提示,CPU并不会每次都满足他。

2.1 yield 和sleep的区别

在JDK1.5以前的版本中yield的方法事实上是调用了sleep(0), 但是它们之间存在着本质的区别,具体如下。

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

三、线程优先级介绍

进程有进程的优先级,线程同样也有优先级,理论上是优先级比较高的线程会获取优先被CPU调度的机会,但是事实上往往并不会如你所愿,设置线程的优先级同样也是一个(提示)hint操作,具体如下。

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

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

设置优先级的方法是setPriorityMAX_PRIORITYMIN_PRIORITY 分别等于101

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

四、获取线程 ID

public long getId()获取线程的唯一ID, 线程的ID在整个JVM进程中都会是唯一的,并且是从0开始逐次递增。如果你在main线程( main函数)中创建了一个唯一的线程, 并且调用getld()后发现其并不等于0,因为在一个JVM进程启动的时候,实际上是开辟了很多个线程,自增序列已经有了一定的消耗,因此我们自己创建的线程绝非第0号线程。

线程在创建的时候,会执行init方法,在最后会执行tid = nextThreadID(); 在调用getId()方法后会返回一个tid

 public long getId() {
        return tid;
    }

五、获取当前线程

public static Thread currentThread()用于返回当前执行线程的引用,这个方法虽然很简单,但是使用非常广泛,来看一段示例代码:

System.out.println(Thread.currentThread());

输出Thread[main,5,main]

六、设置线程上下文类加载器

public ClassLoader getContextClassLoader()获取线程上下文的类加载器,简单来说就是这个线程是由哪个类加器加载的,如果是在没有修改线程上下文类加载器的情况下,则保持与父线程同样的类加载器。

public void setContextClassLoader(ClassLoader cI)设置该线程的类加载器,这个方法可以打破JAVA类加载器的父委托机制,有时候该方法也被称为JAVA类加载器的后门。

七、线程 interrupt

线程interrupt,是一个非常重要的API,也是经常使用的方法,与线程中断相关的API有如下几个

1. public void interrupt()
2. public static boolean interrupted()
3. public boolean isInterrupted()

7.1 interrupt

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

  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方法。
    上述若干方法都会使得当前线程进入阻塞状态,若另外一个线程调用被阻塞线程的interrupt方法,则会打断这种阻塞,因此这种方法有时会被称为可中断方法,记住,打断一个线程并不等于该线程的生命周期结束,仅仅是打断了当前线程的阻塞状态。
    一旦线程在阻塞的情况下被打断,都会抛出一个称为InterruptedException的异常,这个异常就像一个signal (信号)一样通知当前线程被打断了。

interrupt这个方法到底做了什么样的事情呢?

在一个线程内部存在着名为interrupt flag的标识,如果-一个线程被interrupt,那么它的flag将被设置,但是如果当前线程正在执行可中断方法被阻塞时,调用interrupt方法将其中断,反而会导致flag被清除,关于这点我们在后面还会做详细的介绍。另外有一点需要注意的是,如果一个线程已经是死亡状态,那么尝试对其的interrupt会直接被忽略。

7.2 isInterrupted

isInterrupted是Thread的一个成员方法,它主要判断当前线程是否被中断,该方法仅仅是对interrupt标识的一一个判断,并不会影响标识发生任何改变。

7.3 interrupted

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

八、线程join

Thread的join方法同样是一个非常重要的方法,使用它的特性可以实现很多比较强大的功能,与sleep一样它也是一个可中断的方法,也就是说,如果有其他线程执行了对当前线程的interrupt操作,它也会捕获到中断信号,并且擦除线程的interrupt标识,Thread的API为我们提供了三个不同的join方法,具体如下。

  1. public final void join() throws InterruptedException
  2. public final synchronized void join(long millis, int nanos) throws InterruptedException
  3. public final synchronized void join(long millis) throws InterruptedException
package com.example.demo.thread;

/**
 * @author : pengweiwei
 * @date : 2020/2/2 8:19 下午
 */
public class Thread1 extends Thread {

    @Override
    public void run() {
   	 try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
       }
        System.out.println("thread1 runing");
    }
}
package com.example.demo.thread;

/**
 * @author : pengweiwei
 * @date : 2020/2/2 8:19 下午
 */
public class Thread2 extends Thread {

    @Override
    public void run() {
        System.out.println("thread2 runing");
    }
}
package com.example.demo.thread;

/**
 * @author : pengweiwei
 * @date : 2020/2/2 5:35 下午
 */
public class ThreadDemo {

    public static void main(String[] args) throws InterruptedException {
        Thread1 t1 = new Thread1();
        Thread2 t2 = new Thread2();
        t1.start();
        //主线程到这里会阻塞3秒,等待t1执行完成,主线程才会继续执行
        t1.join();
        t2.start();
    }

}

join方法会使当前线程永远地等待下去,直到期间被另外的线程中断,或者join的线程执行结束,当然你也可以使用join的另外两个重载方法,指定毫秒数,在指定的时间到达之后,当前线程也会退出阻塞。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值