Java Thread API

1 Thread API

Thread类方法:
这里写图片描述
主要学习:

  • sleep
  • yield
  • interrupt
  • join

案例中涉及代码 --> 点击跳转

1.1 线程sleep

1.1.1 Thread中的sleep方法

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

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

public static void main(String[] args) {
       //案例用以说明sleep方法只会导致当前线程进行指定时间的休眠
       //启一个线程
       new Thread(() -> {
       //记录匿名线程的休眠时间
       long startTime = System.currentTimeMillis();
       //休眠2000毫秒
       sleep(2_000l);
       long endTime = System.currentTimeMillis();
       System.out.println(String.format("Thread -> Total spend %d ms", (endTime - startTime)));
       }).start();
       //记录main方法线程的休眠时间
       long startTime = System.currentTimeMillis();
       //休眠3000毫秒
       sleep(3_000l);
       long endTime = System.currentTimeMillis();
       System.out.println(String.format("Main -> Total spend %d ms", (endTime - startTime)));
    }

    /**
     * 调用Thread的静态方法sleep
     * 使调用的线程休眠相应的毫秒数
     *
     * @param millis
     */
    private static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

执行结果:

Thread -> Total spend 2000 ms
Main -> Total spend 3000 ms

1.1.2 使用TimeUnit代替Thread.sleep

这里写图片描述
Jdk1.5后引入了枚举TimeUnit,对sleep进行了很好的封装,使用它可以省去时间换算的步骤,并更显优雅:

new Thread(() -> {
            try {
                //使用Thread.sleep 休眠1秒
                Thread.sleep(1000l);
                //使用TimeUnit纳秒 休眠1微秒
                TimeUnit.NANOSECONDS.sleep(1000l);
                //使用TimeUnit微秒 休眠1毫秒
                TimeUnit.MICROSECONDS.sleep(1000l);
                //使用TimeUnit毫秒 休眠1秒
                TimeUnit.MILLISECONDS.sleep(1000l);
                //使用TimeUnit秒 休眠1分钟
                TimeUnit.SECONDS.sleep(60l);
                //使用TimeUnit分钟 休眠1分钟
                TimeUnit.MINUTES.sleep(1l);
                //使用TimeUnit小时 休眠1天
                TimeUnit.HOURS.sleep(24l);
                //使用TimeUnit天 休眠1天
                TimeUnit.DAYS.sleep(1l);

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

1.2 线程yield

1.2.1 Thread中的yield方法

  • public static native void yield();

yield ([ji:ld放弃) 方法属于一种启发式方法,其会提醒调度器本线程愿意放弃当前的CPU资源,如果CPU资源不紧张,则会忽略这个提醒;
调用yield方法回事当前线程从RUNNING状态转换至RUNNABLE状态

Created with Raphaël 2.2.0 RUNNING RUNNABLE

案例中如果注释if判断,执行结果总是正常的顺序,如果打开则顺序会错乱:

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

    }

    private static Thread create(int index) {
        return new Thread(() -> {
            //注释if判断 总是正常的顺序
            if (index == 0)
              Thread.yield();
            System.out.println(Thread.currentThread().getName() + ":" + index);
        });
    }

执行结果:

//前两顺序乱了
Thread-1:1
Thread-0:0
Thread-2:2
Thread-3:3
Thread-4:4
Thread-5:5
Thread-6:6
Thread-7:7
Thread-8:8
Thread-9:9

1.2.2 yield和sleep

JDK1.5以前yield方法实际上时调用了sleep(0)方法,但他们存在本质的区别:

  • sleep会导致当前线程暂停指定的时间,没有CPU时间片的消耗
  • yield只是对CPU调度器的一个提示,如果CPU未忽略,会导致线程上下文的切换
  • sleep会使线程短暂block,会在给定的时间内释放CPU资源
  • yield会使线程从RUNNING转至RUNNABLE状态
  • sleep会百分之百完成给定时间的休眠,而yield不一定
  • 一个线程sleep另一个线程调用interrupt会不会到中断信号,而yield则不会

1.3 线程优先级

1.3.1 Thread中的优先级

  • public final void setPriority(int newPriority)
  • public final int getPriority()

进程有进程的优先级,线程也有优先级,理论上优先级比较高的线程会获得先被CPU调度的机会,事实上可能不会如你所愿:

  • 设置线程优先级只是一个对CPU的hint[提示]操作,提示CPU某个线程要优先执行
  • 两个优先级不同的线程同时执行,在不同的CPU资源情况下结果可能会不同

让两个优先级不同的线程一起跑,优先级大的出现概率会大一些:

public static void main(String[] args) {
        Thread thread0 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("-" + Thread.currentThread().getName() + "-");
                }
            }
        });
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("-" + Thread.currentThread().getName() + "-");
                }
            }
        });
        thread0.setPriority(2);
        thread1.setPriority(10);
        thread0.start();
        thread1.start();
    }

1.3.2 优先级源码分析

setPriority方法源码:

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

结论:

  • 优先级取值范围0~10
  • 优先级的取值需要在所在threadGroup的范围内,如果大于所在threadGroup优先级最大值,则取其最大值
  • 一般都不会进行优先级的设置,因为在不同CPU资源下结果会有不同
  • 在不设置优先级的情况下,其优先级与其父类保持一致

1.4 获取当前线程&线程ID

  • public static native Thread currentThread()
  • public long getId()

方法acurrentThread():返回当前线程的引用,使用非常广泛;
方法getId():获取线程的唯一ID,线程ID在JVM进程中都会是唯一的,并且时从0开始递增的;
这里写图片描述

1.5 线程interrupt

线程内部存在一个interrupt flag

  • 有很多方法可以将线程置于阻塞状态下
  • 当调用interrupt()方法后,线程interrupt flag将被赋值true
  • 当线程处于阻塞状态下,调用iterrupt()方法时,会打断当前线程的阻塞并抛出InterruptedException,可中断方法捕获到异常后会将interrupt flag檫除(置为false)
  • 当一个线程已经结束后,再调用interrupt()方法会被忽略

1.5.1 线程进入阻塞状态

调用如下方法可以使线程进入阻塞状态:

  • Object的wait方法
  • Thread的sleep方法
  • Thread的join方法
  • InterruptibleChannel的io操作
  • Selector的wakeup方法

此时,若另外一个线程调用被阻塞线程的interrupted()中断方法,则会打断这种阻塞状态,并抛出一个InterruptedException异常:

1.5.2 线程的interrupt方法

  • public static boolean interrupt()

调用当前线程的interrupted()方法,可以打断当前线程的阻塞:

public static void main(String[] args) throws InterruptedException {
        //new一个线程
        Thread thread = new Thread(() -> {
            try {
                //休眠5分钟
                TimeUnit.MINUTES.sleep(5l);
            } catch (InterruptedException e) {
                //被打断时打印
                System.out.println("fuck! i am be interrupted.");
            }
        });
        //启动线程
        thread.start();
        //主线程休眠5s
        TimeUnit.SECONDS.sleep(5l);
        //调用thread的interrupt方法 打断thread
        thread.interrupt();
    }

执行过程:
这里写图片描述
执行结果:

fuck! i am be interrupted.

1.5.2 线程的isInterrupted方法

  • public boolean isInterrupted()

isInterrupted()方法用于判断当前线程是否被中断,该方法只是对interrupted flag的一个判断,并不会影响flag发生任何变化;

public static void main(String[] args) throws InterruptedException {
        //new一个线程
        Thread thread = new Thread(() -> {
            //控循环
            while (true) {
                //不进行sleep 因为可中断方法捕捉到中断信号后,会将其檫除
            }
        });

        //启动线程
        thread.start();
        //主线程休眠2ms
        TimeUnit.MILLISECONDS.sleep(2l);
        //调用thread的interrupt方法 打断thread
        System.out.printf("Thread is interrupted ? %s\n", thread.isInterrupted());
        //调用thread的interrupt方法 打断thread,并将interrupt flag置为true
        thread.interrupt();
        System.out.printf("Thread is interrupted ? %s\n", thread.isInterrupted());
    }

执行结果:

Thread is interrupted ? false
Thread is interrupted ? true

将程序修改一下:

    public static void main(String[] args) throws InterruptedException {
        //new一个线程
        Thread thread = new Thread(() -> {
            try {
                //休眠5分钟
                TimeUnit.MINUTES.sleep(5l);
            } catch (InterruptedException e) {
                //被打断时打印
                //可中断方法捕捉到中断信号后,会将其檫除
                System.out.println("fuck! i am be interrupted.");
            }
        });
        //设置为daemon,让jvm自动退出
        thread.setDaemon(true);
        //启动线程
        thread.start();
        //主线程休眠3s
        TimeUnit.SECONDS.sleep(3l);
        //调用thread的interrupt方法 打断thread
        System.out.printf("Thread is interrupted ? %s\n", thread.isInterrupted());
        //调用thread的interrupt方法 打断thread,并将interrupt flag置为true
        thread.interrupt();
        //休眠3秒等待sleep被打断捕获异常后檫除interrupt flag
        TimeUnit.SECONDS.sleep(3l);
        System.out.printf("Thread is interrupted ? %s\n", thread.isInterrupted());
    }

执行结果:

Thread is interrupted ? false
fuck! i am be interrupted.
Thread is interrupted ? false

1.5.3 线程的interrupted方法

  • public static boolean interrupted()

interrupted是一个静态方法:

  • 用于判断当前线程是否被中断
  • 调用该方法会擦除线程的interrpute flag
  • 线程被打断后,第一次调用返回true,并将flag擦除,第二次以后的调用永远返回false
public static void main(String[] args) throws InterruptedException {
        //new一个线程
        Thread thread = new Thread(() -> {
            while (true) {
                //第一次调用 返回true 并檫除interrput flag
                //第二次以后只返回false了
                System.out.println(Thread.interrupted());
            }
        });
        //设置为daemon,让jvm自动退出
        thread.setDaemon(true);
        //启动线程
        thread.start();
        //主线程休眠3s
        TimeUnit.SECONDS.sleep(1l);
        //调用thread的interrupt方法 打断thread,并将interrupt flag置为true
        thread.interrupt();
    }

interrupt()被调用后,flag被置为true,调用interrupted(),第一次返回flage的值,并将其差擦,后边再调用只返回flag的值,执行结果:

....
false
false
false
true
false
false
false
....

1.5.4 interrupt注意事项

  • private native boolean isInterrupted(boolean ClearInterrupted)
    这里写图片描述

interrupted方法和isInterrupt方法和都调用了同一个本地方法isInterrupted(boolean ClearInterrupted),isInterrupt传入false(不清除flag),interrupted传入true(清除flag);

1.6 线程join

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

线程B join 线程A,线程B会进入等待,直到线程A生命周期结束,或者到达指定的时间,这段时间内线程B处于BLOCKED状态:

    public static void main(String[] args) throws InterruptedException {
        //1 创建2个线程
        List<Thread> threadList = IntStream.range(1, 3)
                .mapToObj(ThreadJoin::createThread).collect(Collectors.toList());
        //2 启动线程
        threadList.forEach(Thread::start);

        //3 main方法线程调用这两个线程的join方法
        for (Thread thread : threadList) {
            thread.join();
        }
        //4 mian方法线程会等上边两个线程执行完后才进行打印
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
            sleepForMoment();
        }
    }

    /**
     * 创建线程 并休眠5次 2s
     *
     * @param index
     * @return
     */
    private static Thread createThread(int index) {
        return new Thread(() -> {
            Thread.currentThread().setName("weixx" + index);
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + "-->" + i);
                sleepForMoment();
            }

        });
    }

    /**
     * 休眠2秒
     */
    private static void sleepForMoment() {
        try {
            TimeUnit.SECONDS.sleep(2l);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

执行结果:
这里写图片描述
完整结果:

weixx1-->0
weixx2-->0
weixx1-->1
weixx2-->1
weixx1-->2
weixx2-->2
weixx1-->3
weixx2-->3
weixx1-->4
weixx2-->4
main-->0
main-->1
main-->2
main-->3
main-->4

现在将3出的join方法注掉,会看到weixx-1、weixx-2和main方法线程交替执行:
这里写图片描述

1.7 线程的关闭

1.7.1 正常关闭

1.7.1.1 线程声明周期正常结束

逻辑单元执行完逻辑后,线程结束生命周期,就会正常退出,这是线程的正常结束;

1.7.1.2 捕获中断信号关闭线程

通过标记来控制逻辑单元的结束,来结束线程生命周期:

public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println("我正在运行...");
            while (!Thread.currentThread().isInterrupted()) {
                //空循环
            }
            System.out.println("我还在运行...");
        });

        thread.start();
        TimeUnit.SECONDS.sleep(8l);
        System.out.println("线程即将被关闭...");
        thread.interrupt();
    }

执行结果:

我正在运行...

线程即将被关闭...
我还在运行...

Thread-0一直在空循环,直到main主线程休眠结束,执行interrupt():
在这里插入图片描述

1.7.1.3 通过volatile开关控制

思路跟中断标记一样,都是做一个标记,当这个标记为true时,让执行单元结束来结束线程生命周期;

1.7.2 异常关闭

1.7.2.1 异常退出

线程的执行单元中是不允许跑出cheched异常的,不论Thread中的run方法试试Runnable中的run方法,如果需要在运行过程中捕获checked异常并判断是否有执行下去的必要,那么可以将checked异常封装成unchecked(RuntimeException)异常抛出去进而结束线程生命周期;

1.7.2.2 进程假死

进程假死时,看不到任何逻辑的执行,就像死了一样,大部分原因是某个线程阻塞了,或是出现了死锁的情况,这时候需要借助jstack、jconsole等工具来进行核查诊断,线程假死生命周期并没有结束。

参考文献:
[ 1 ] Java高并发编程详解 汪文君著。–北京:机械工业出版社,2018年6月第1版

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值