并发编程——Thread API解析

Thread API

1 sleep

正常Thread中的sleep方法时将当前线程进入休眠,暂停执行,虽然给定了一个休眠的时间,但是最终要以系统的定时时间和调度器的精度为准,休眠不会放弃monitor锁的所有权。

也可以看看jdk1.8中官方文档中的解释:

static void sleep(long millis) 
使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。  
static void sleep(long millis, int nanos) 
导致正在执行的线程以指定的毫秒数加上指定的纳秒数来暂停(临时停止执行),这取决于系统定时器和调度器的精度和准确性。 

接下来看看java 源码中是如何实现的

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

没有具体实现的原因,可以查看navive和JNI的解释(个人理解:就是通过调用jdk实现,说白了就是有人给你实现好了,你直接使用就行,)

通过构造函数,可以知道sleep是通过毫秒和纳秒来初始化暂停时间的,这也就是说,你需要进行时间的转换。

替换方法:TimeUnit封装了sleep的使用


列子:
TimeUnit.HOURS.sleep(1);
TimeUnit.SECONDS.sleep(5);

2 yield

首先来看看在java源码中的注释:

向调度程序提示当前线程愿意放弃其当前对处理器的使用。调度程序可以随意忽略此提示。 Yield 是一种启发式尝试,旨在改善线程之间的相对进展,否则会过度使用 CPU。它的使用应与详细的分析和基准测试相结合,以确保它确实具有预期的效果。很少适合使用这种方法。它对于调试或测试目的可能很有用,它可能有助于重现由于竞争条件引起的错误。在设计并发控制结构(例如 java.util.concurrent.locks 包中的结构)时,它也可能很有用。

也就是说yield是一种启发式的方法,会提示调度器愿意放弃当前cpu的资源,如果cpu资源不紧张,会忽略这种提示。

调用yield会使当前线程从RUNNING状态切换至RUNNABLE状态,一般不常用。

yield和sleep对比:

  1. sleep会导致当前线程暂停指定的时间,这段时间是没有CPU的消耗;
  2. yield只是对CPU调度器的一个提示,如果没有忽略这个提示,它会导致线程的上下文的切换。
  3. sleep会使线程短暂block,会在给定的时间释放CPU资源。
  4. 当CPU调度器没有忽略yield的命令,则会使线程从RUNNING状态切换到RUNNABLE状态。
  5. sleep是一定会保证休眠一定时间,但是yield不一定。
  6. 一个线程sleep另一个线程调用interrupt会捕获到中断信号,而yield不会。

3 线程优先级

理论上线程优先级会优先获取被CPU调度的机会,但是,事实上这也是提示操作,当cpu资源比较紧张的时候,可能会如你所愿,但是空闲的CPU对此操作,可以说是毫无作用。

线程的优先级设置源码

/**
 * 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;
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;
线程的优先级依赖于父类线程,一般main线程的优先级是5,所以派生出来的线程一般优先级也是5;

4 线程ID

public long getId() {
    return tid;
}

获取线程id,线程ID是整个JVN进程中都会是唯一的,从0开始自增。

5 获取当前线程

JDK文档:
static Thread currentThread() 
返回对当前正在执行的线程对象的引用。 
java中代码:
public static native Thread currentThread();

用于放回当前线程的引用,方法简单,但是比较常用。

6 设置上下文类加载器

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



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

7 线程interrupt **

方法

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

interrupt

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

  1. Object的wait
  2. Object的wait(long)
  3. Object的wati(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方法,就可打断阻塞。

这种方法有时会被成为可中断方法,这个打断不是结束当前线程的生命周期,而是打断当前线程的阻塞状态。

一个线程阻塞的情况下被打断,会抛出一个InterruptedException的异常,这个异常像是一个signal(信号)一样通知当前线程被打断了

isInterrupted

主要判断当前线程是否被中断

interrupted

interrupted是一个静态方法,虽然也用与判断当前线程是否被中断,但是他与成员方法isInterrupted有很大区别,调用该方法会直接擦出掉线程的interrupt表示,需要注意的是,如果当前线程被打断,那么第一次调用interrupted方法会返回true,并且立即擦除interrupt表示;第二次以及之后的调用都会返回false,除非此期间线程又一次被打断。

对比:

public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}
public boolean isInterrupted() {
    return isInterrupted(false);
}
private native boolean isInterrupted(boolean ClearInterrupted);

可以看出来,interrupted()和isInterrupted()都是调用isInterrupted方法来实现的,区别就是是否擦除interrupt标识;

8 join

  • 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

与sleep相同,同样是一个可中断的方法。

join理解:
join某个线程A, 还是其他线程进入等待,直到线程A生命周期结束,或者到达给定的时间,那么此期间其他线程处于BLOCKED,而不是A线程;

个人理解:
join A线程,相当于,屏蔽其他线程的执行,当前只执行A线程的代码。

当然,这其中如果同时启动了两个线程之后,在join,两个线程会交替运行之后,在运行其他线程;

例子:
Thread05

package com.spring.zcl.study.springbootstudy.thread;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
 * @Author: zcl
 * @Date: 2021-12-17 10:04
 */
public class Thread05Join {
    public static void main(String[] args) throws InterruptedException {
        List<Thread> collect = IntStream.range(1, 4).mapToObj(Thread05Join::create).collect(Collectors.toList());
        collect.forEach(Thread::start);
        for (Thread thread : collect) {
            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######0
3######0
2######0
1######1
3######1
2######1
3######2
1######2
2######2
1######3
3######3
2######3
1######4
3######4
2######4
2######5
3######5
1######5
1######6
3######6
2######6
1######7
2######7
3######7
1######8
3######8
2######8
1######9
3######9
2######9
main######0
main######1
main######2
main######3
main######4
main######5
main######6
main######7
main######8
main######9

从结果可以看出来,join调用之后,线程虽然堵塞了,但是由于其他线程也已经strat启动了,所以仍然在执行,主线程是等其余线程完成之后,才开始执行;

我们修改一下,看看效果,启动其中一个线程,然后join的效果:

List<Thread> collect = IntStream.range(1, 4).mapToObj(Thread05Join::create).collect(Collectors.toList());

    collect.get(0).start();

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

    collect.get(1).start();
    collect.get(2).start();

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

我们将main方法中的start部分代码进行修改如上,执行如下:

1######0
1######1
1######2
1######3
1######4
1######5
1######6
1######7
1######8
1######9
main######0
2######0
3######0
2######1
3######1
main######1
main######2
3######2
2######2
2######3
3######3
main######3
3######4
main######4
2######4
3######5
2######5
main######5
main######6
3######6
2######6
main######7
3######7
2######7
3######8
2######8
main######8
2######9
main######9
3######9

可以看出只有Thread1执行之后,其余线程与主线程才开始执行,并且是乱序输出。

9 如何关闭一个线程

JDK有一个Deprecated方法stop,但是该方法存在一个问题,JDK官方不建议使用,后续版本中有可能会被移除,根据官方描述,该方法在关闭线程的时候不会释放monitor的锁。

正常关闭

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

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

  3. 使用volatile开关控制

异常退出

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

进程假死

假死就是进程虽然存在,但是没有日志输出,程序不进行任何的作业,看起来就像死了一样,但事实是没有死,程序出现这样的情况,大多数的情况都是因为某个线程阻塞了,或者线程出现了死锁的情况。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值