三、JAVA多线程:Thread API详细介绍 (sleep、Interrupt、Join、TimeUnit、yield、interrupted、线程优先级 )

 本章深入分析了Thread的所有API,熟练掌握Thread的API是学好Thread的前提。

线程sleep

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

sleep方法介绍

static void sleep(long millis)

Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers.

static void sleep(long millis, int nanos)

Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds plus the specified number of nanoseconds, subject to the precision and accuracy of system timers and schedulers.

sleep方法会使当前线程进入指定毫秒数的休眠,暂停执行,虽然给定了休眠时间,但是最终要以系统的定时器和调度器的精度为准

不会放弃monitor锁的所有权。 每个线程的休眠互不影响。

使用TimeUnit代替Thread.sleep 

TimeUnit.DAYS          //天

TimeUnit.HOURS         //小时

TimeUnit.MINUTES       //分钟

TimeUnit.SECONDS       //秒

TimeUnit.MILLISECONDS  //毫秒

TimeUnit.SECONDS.sleep( 5 );

TimeUnit.MINUTES.sleep( 5 );

TimeUnit.HOURS.sleep( 5 );

 

线程yield

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

调用yield方法会使当前线程从RUNNING状态切换到RUNNABLE状态

yield和sleep

sleep会导致当前线程暂停指定的时间,没有cpu时间片的消耗

yield只是对CPU调度器一个提示,如果CPU没有忽略这个提示,它会导致线程上下文的切换

sleep会使线程断站block,会在给定的时间内释放CPU资源

yield会使RUNNING状态的Thread进入RUNNABLE状态(如果CPU没有忽略这个提示)

sleep机会百分百万恒了给定时间的休眠,但是yield的提示并不一定能担保

一个线程的sleep另一个线程调用interrupt会捕获到中断信号,而yield则不会。

设置线程的优先级

void setPriority(int newPriority)

变更线程的优先级

int getPriority()

返回线程的优先级

线程优先级介绍

理论上优先级比较高的线程会优先被CPU调度。

对于root用户,他会hint操作系统你要设置的优先级,否则会忽略

CPU繁忙,设置优先级获取更多的CPU时间,但是CPU空闲,几乎不起作用

不建议在程序设置的时候,使用线程优先级绑定某些特定的业务,或者让业务严重依赖于线程优先级。‘

线程优先级源码分析

直接查看setPriority方法

/**
 * Changes the priority of this thread.
 * <p>
 * First the <code>checkAccess</code> method of this thread is called
 * with no arguments. This may result in throwing a
 * <code>SecurityException</code>.
 * <p>
 * Otherwise, the priority of this thread is set to the smaller of
 * the specified <code>newPriority</code> and the maximum permitted
 * priority of the thread's thread group.
 *
 * @param newPriority priority to set this thread to
 * @exception  IllegalArgumentException  If the priority is not in the
 *               range <code>MIN_PRIORITY</code> to
 *               <code>MAX_PRIORITY</code>.
 * @exception  SecurityException  if the current thread cannot modify
 *               this thread.
 * @see        #getPriority
 * @see        #checkAccess()
 * @see        #getThreadGroup()
 * @see        #MAX_PRIORITY
 * @see        #MIN_PRIORITY
 * @see        ThreadGroup#getMaxPriority()
 */
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);
    }
}

优先级等级  默认 5  最小1,最大 10 。

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

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

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

关于线程优先级总结

一般不建议对线程设定优先级别更不会让某些业务严重依赖线程的优先级别,比如权重。借助优先级设定某个任务的权重,这种方式不可取,建议使用默认优先级( 5 )。 没有显示指定, 子线程与父线程优先级保持一致。

获取线程ID

获取线程的唯一ID,在整个JVM进程中都是唯一的从0开始逐次递增。

long getId()

Returns the identifier of this Thread.

获取当前线程

static Thread currentThread()

返回当前执行线程的引用

设置线程上下文类加载器

ClassLoader getContextClassLoader()

Returns the context ClassLoader for this Thread.

获取线程上下文的类加载器,如果没有修改类的上下文加载器,则保持与父线程同样的类加载器

void setContextClassLoader(ClassLoader cl)

Sets the context ClassLoader for this Thread.

设置线程的类加载器,打破JAVA类加载器的父委托机制。

线程Interrupt

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

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方法,则会打断这种阻塞,因此这种方法优势会被成为可中断的方法。

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

一旦线程在阻塞的情况下被打断,就会抛出一个InterruptedExeception的异常,这个异常就像一个singnal(信号)一样通知当前线程被打断了。

 

在一个线程内部存在着名为interrupt flag的标识, 如果一个线程被interrupt,那么他的flag将会被设置,当时如果当前线程正在执行可以中断的方法被阻塞是,将会调用interrupt方法将其中断,反而会导致flag被清除。线程已经死亡的话,interrupt将会忽略。

isInterrupted

Thread的一个成员方法,判断当前线程是否被中断

中断方法捕获了中断信号之后,为了不影响线程的其他方法执行,会将线程的interrupt标识复位。

所以上面所有的输出都是 false

interrupted

用于判断当前线程是否被中断,会直接擦除线程中的interrupt标识。

如果当前线程被打断了,那么调用第一次interrupted方法会直接返回true,并且立即擦除了interrupt标识。

第二次包括以后的调用都会永远的放回false


 

区别interrupted 和isInterrupted

1. Thread.interrupted() 第一次返回true ,第二次编程false ,说明interrupted会直接擦除线程中的interrupt标识。

2.isInterrupted 调用两次,都是true 说明isInterrupted会直接擦除线程中的interrupt标识。

 

如上图,调用 Thread.interrupted() 第一次返回true ,第二次编程false ,说明interrupted会直接擦除线程中的interrupt标识。

isInterrupted 调用两次,都是true 说明isInterrupted会直接擦除线程中的interrupt标识。

interrupt注意事项

isInterrupted和interrupted方法都调用了同一个本地方法:

/**
 * Tests if some Thread has been interrupted.  The interrupted state
 * is reset or not based on the value of ClearInterrupted that is
 * passed.
 */
private native boolean isInterrupted(boolean ClearInterrupted);

ClearInterrupted是用用来控制是否擦除线程的interrupt的标识。

isInterrupted方法的源码中该参数设置为false,标识不想擦除。

线程Join

与sleep一样,他也是一个可中断的方法,也就是说,如果有其他线程执行了对当前线程的interrupt操作,他也会捕捉到中断信号,并且擦除线程的interrupt标识,ThreadAPI为我们提供了三种不同的join方法。具体如下

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

线程join方法详解

join某个线程A, 会使当前线程B进入等待,直到线程A结束生命周期,或者达到给定的时间。

在此期间线程B是处与BLOCKED状态。

基本用法:


import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

import static java.util.stream.Collectors.toList;

public class ThreadJoin {
    public static void main(String[] args) throws InterruptedException {

        List<Thread> threads = IntStream.range(1,3).mapToObj(ThreadJoin::create).collect(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();
        }

    }

}

输出不同的结果:

结论:

创建两个线程,分别启动,并调用每个线程的join方法, 线程1和线程2交替输出,知道生命周期结束。main线程才执行。

join方法会使当前线程永远的等待下去,知道期间被另外的线程中断,或者join的线程结束,

当前也可以使用它另外的重载方法。指定毫秒数,在指定的时间到达之后,线程退出阻塞。

join方法结合实践

客户获取多个航空公司的航班信息

代码:

import java.util.List;

public interface FightQuery {
    List<String> get() ;
}
package com.zl.step3.Flght;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

public class FightQueryTask extends Thread implements FightQuery {

    private final String origin ;


    private final String destionation  ;

    private final List<String> flightList = new ArrayList<>();


    public FightQueryTask(String airline , String origin , String destionation) {
        super("["+airline+"]");
        this.origin = origin;
        this.destionation = destionation ;
    }

    @Override
    public void run(){
        System.out.printf("%s-query form %s to %s \n" , getName() , origin , destionation) ;

        int randomVal = ThreadLocalRandom.current().nextInt(10);

        try {
            TimeUnit.SECONDS.sleep(randomVal);

            this.flightList.add(getName()+" - " + randomVal);

            System.out.printf("The Fight: %s list query sucessful \n" , getName());


        } catch (InterruptedException e) {
            e.printStackTrace();
        }


    }


    @Override
    public List<String> get(){
        return  this.flightList;
    }



}
package com.zl.step3.Flght;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static java.util.stream.Collectors.toList;

public class FlightCompanyExample {

    //合作的各大航空公司
    private static List<String> fightCompany = Arrays.asList("CSA","CEA","HNA") ;



    public static void main(String[] args) {

        List<String> results = search("SH","BJ");

        System.out.println("===========resoult=================");

        results.forEach(System.out :: println);

    }

    private static List<String> search(String original, String dest) {

        final List<String> result = new ArrayList<>() ;

        // 查询航班信息的线程列表
        List<FightQueryTask> tasks = fightCompany.stream().map(f -> createSearchTask(f,original,dest)).collect(toList());

        // 启动线程
        tasks.forEach(Thread::start);


        // 阻塞线程
        tasks.forEach(t -> {
            try{
                t.join();
            }catch (InterruptedException e){

            }

        });

        // 输出结果
        tasks.stream().map(FightQuery::get).forEach(result::addAll);

        return result ;


    }


    private static FightQueryTask createSearchTask(String fight , String original, String dest ){
        return new FightQueryTask(fight,original,dest);
    }




}

输出结果:

如何关闭一个线程

JDK中有一个Deprecated方法stop, 这个方法存在问题,不推荐使用

该方法在关闭线程时可能不会释放掉monitor的锁,不推荐!!!!

@Deprecated
public final void stop() {
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        checkAccess();
        if (this != Thread.currentThread()) {
            security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
        }
    }
    // A zero status value corresponds to "NEW", it can't change to
    // not-NEW because we hold the lock.
    if (threadStatus != 0) {
        resume(); // Wake up thread if it was suspended; no-op otherwise
    }

    // The VM can handle all thread states
    stop0(new ThreadDeath());
}

正常关闭

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

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

    通过检查interrupt的标识来决定是否退出。

import java.util.concurrent.TimeUnit;

public class InterruptThreadExit {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread() {
            @Override
            public void run(){
                System.out.println("I whill start work ");

                while (!isInterrupted()) {
//                    System.out.println(" I'm working....");


                }

                System.out.println("I will be exiting .");
            }

        };

        t.start();
        TimeUnit.SECONDS.sleep(1);

        System.out.println("System will be shutdown .");
        t.interrupt();

    }


}

运行截图:

3.使用volatile 开关控制

由于interrupt标识很有可能被擦除,或者逻辑单元中不会调用任何中断方法,

所以使用volatile修饰的开关flag关闭线程也是一种常用的做法:

import java.util.concurrent.TimeUnit;

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()) {
//                    System.out.println(" I'm 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.SECONDS.sleep(1);

        System.out.println("System will be shutdown .");
        t.close();

    }


}

异常退出

进程假死

进程虽然存在,但是没有日志输出,程序不做任何作业,实际并没有死,大部分原始是因为阻塞或者死锁。

可以用jvisualvm,jmap等工具进行查看

本文整理来源于:

《Java高并发编程详解:多线程与架构设计》 --汪文君


 

猜你喜欢

转载自blog.csdn.net/zhanglong_4444/article/details/85951495
今日推荐