谈谈我对Java线程的了解

2020-02-21  本文已影响0人  雪狼_lykos
世界那么大,我想带你去走走

本篇文章主要介绍java中线程和线程池的使用


你想拥有我,必须先了解我

一、线程

1. 线程分类

继承 java.lang.Thread

public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("任务执行");
    }
}

实现 java.lang.Runnable

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("方法执行");
    }
}

Thread与Runnable区别

守护线程与非守护线程

java线程中分为守护线程用户线程(非守护线程)。当程序中所有的用户线程都结束了,那么程序也就退出了,换言之只要有一个用户线程还活着,程序也就不会退出。
我们一般默认创建的线程就是用户线程,如果想要改变成守护线程可以调用threadA.setDaemon(true),注意此方法必须在start()方法之前调用,还有就是守护线程中创建的线程也是守护线程。
下面 举个栗子

Thread t1 = new Thread(()->{
    try{
        Thread.sleep(60000);
    }catch (Exception ex){
        ex.printStackTrace();
    }
});
System.out.println("thread 1 start执行前结果---->"+t1.getState());
//此方法如果放开程序会很快结束,如果注释,程序会大概等待60秒才结束
//t1.setDaemon(true);
t1.start();
Thread.sleep(200);
System.out.println("thread 1 start执行后结果---->"+t1.getState());

上面的栗子会大概等待运行60秒结束,如果放开t1.setDaemon(true)注释,程序会很快结束。
注意上面的栗子如果用Junit跑也会很快退出,因为Junit Runner执行主线程完成后,会主动退出程序

2. 线程的生命周期

java.lang.Thread.State中定义了6种线程状态,分别为NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED
下面这张图对jvm线程状态和生命周期做了很好的展示。

图片来源于[http://www.bubuko.com/infodetail-84667.html](http://www.bubuko.com/infodetail-84667.html)
接下来我们分别分析一下这些线程状态
Thread thread = new Thread(()->{while (true){}});
System.out.println(thread.getState());
----> 运行结果: NEW
Thread thread = new Thread(()->{while (true){}});
thread.start();
System.out.println(thread.getState());
----> 运行结果: RUNNABLE
Object lock = new Object();
Runnable run = ()->{
    synchronized (lock){
        while (true){}
    }
};
Thread thread1 = new Thread(run);
Thread thread2 = new Thread(run);
thread1.start();
thread2.start();
System.out.println("运行结果--->thread1:"+thread1.getState());
System.out.println("运行结果--->thread2:"+thread2.getState());

运行结果--->thread1:RUNNABLE
运行结果--->thread2:BLOCKED
相关线程栈信息

Object.wait with no timeout
执行ObjectA.wait方法时必须获取到monitor锁,因此wait方法需要放到synchronized 同步方法块中,当然调用notify() or notifyAll也必须要在同步方法块中。
注意
如果ObjectA是线程对象时,那么只要ObjectA线程执行完成后,会自动调用notifyAll方法

Object obj = new Object();
Thread thread = new Thread(()->{
    try {
        synchronized (obj) {
            obj.wait();
        }
    }catch (InterruptedException ex){}
});
thread.start();
while (true){
    Thread.sleep(1000);
    System.out.println(thread.getState());
}

执行结果---->WAITING
执行结果---->WAITING
执行结果---->WAITING
调用Object.wait()所处线程状态

Thread.join with no timeout
thread.join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。join内部原理是调用了Object.wait方法,使得两个线程间有了通讯方式,从而达到顺序执行的目的。

Thread t1 = new Thread(()->{
    try {
        Thread.sleep(5000);
        System.out.println("this is thread 1");
    }catch (InterruptedException ex){}
});
Thread t2 = new Thread(()->{
    try {
        t1.join();
        System.out.println("this is thread 2");
    }catch (InterruptedException ex){}
});
////t1,t2执行顺序可以任意
t1.start();
t2.start();
while (true){
    Thread.sleep(1000);
    System.out.println("thread 1 执行结果---->"+t1.getState());
    System.out.println("thread 2 执行结果---->"+t2.getState());
}

thread 1 执行结果---->TIMED_WAITING
thread 2 执行结果---->WAITING
this is thread 1
this is thread 2
thread 1 执行结果---->TERMINATED
thread 2 执行结果---->TERMINATED

thread.join()的线程状态
LockSupport.park
LockSupport.park()休眠当前线程进入阻塞状态,直到得到许可证(也就是调用LockSupport.unpark(thread1))后继续执行。
注意
unpark可以优先于park执行,但是许可证不会进行累加,也就是说在执行park前无论执行多少次unpark,获得的park许可证只有一次。在线程启动之前调用 park/unpark方法没有任何效果
Thread t1 = new Thread(()->{
    LockSupport.park();
});

t1.start();
while (true){
    Thread.sleep(1000);
    System.out.println("thread 1 执行结果---->"+t1.getState());
}

thread 1 执行结果---->WAITING
执行LockSupport.park()的线程状态

由于在WAITING中已经举过类似例子,他们用法都是一样的,只是多了一个 超时时间。在这里就不一 一举例了,只演示Thread.sleep

Thread t1 = new Thread(()->{
    try{
        Thread.sleep(1000000);
    }catch (Exception ex){}
});
t1.start();
while (true){
    Thread.sleep(1000);
    System.out.println("thread 1 执行结果---->"+t1.getState());
}

thread 1 执行结果---->TIMED_WAITING
sleep 指定时间内的线程状态
Thread t1 = new Thread(()->{
    try{
        int i = 1 / 0;
    }catch (Exception ex){
        ex.printStackTrace();
    }
});
System.out.println("thread 1 start执行前结果---->"+t1.getState());
t1.start();
while (true){
    System.out.println("thread 1 start执行后结果---->"+t1.getState());
    Thread.sleep(1000);
}

执行结果

thread 1 start执行前结果---->NEW
thread 1 start执行后结果---->RUNNABLE
java.lang.ArithmeticException: / by zero
    at lykos.demo.ThreadDemo.lambda$main$0(ThreadDemo.java:124)
    at java.lang.Thread.run(Thread.java:748)
thread 1 start执行后结果---->TERMINATED

从结果可以看出线程异常退出后,会进入TERMINATED状态

3. 线程间通讯

线程间的通讯可以用以下几种方式,由于篇幅问题就不在这里做具体的例子,和对比。

二、线程池

作用

要点

public ThreadPoolExecutor(int corePoolSize,  //核心线程数
                              int maximumPoolSize,  //最大线程数
                              long keepAliveTime, //线程存活时间
                              TimeUnit unit,  //线程存活时间单位
                              BlockingQueue<Runnable> workQueue, // 任务队列
                              ThreadFactory threadFactory, // 创建线程的工场类
                              RejectedExecutionHandler handler)// 任务拒绝策略

理解了上面的解释,接下来我们做一个例子

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
    2,//核心线程数为2
    4,//最大线程数为4
    60,//空闲线程存活时间
    TimeUnit.SECONDS,//空闲线程存活时间单位
    new LinkedBlockingQueue<>(10),//存放任务队列
    Executors.defaultThreadFactory(),//创建线程工场
    new ThreadPoolExecutor.AbortPolicy());//当任务队列饱和,也达到最大线程数后拒绝任务策略
    //如果设置为true 允许核心线程也可以回收,回收时间就是设置的 空闲线程存活时间 60
    //threadPoolExecutor.allowCoreThreadTimeOut(true);
threadPoolExecutor.execute(()->{
    System.out.println("this execute task");
});
Future future = threadPoolExecutor.submit(()->{
    System.out.println("this submit method task");
    return 1;
});

如何执行任务
ThreadPoolExecutor有个核心内部类Worker真正执行线程,创建线程定义的核心线程数,最大线程数其实就是这个Worker类的数量。
上面的例子值得注意的是当我们创建好一个线程池后,有两种方法提交任务executesubmit他们内部本身没有太大区别,都是通过Worker对象来执行任务,唯一的区别就是submit支持返回值,且返回值是Future对象。下面介绍一下提交任务内部的大致逻辑。

1.判断工作线程数是否达到核心线程数,如果没有则创建新的工作线程
2. 判断是否队列中任务满
    2.1 未满
        判断是否工作线程数达到最大线程数
        2.1.1 达到
            不做处理
        2.1.2 未达到
            创建并开启新的工作线程
    2.2 已满
        判断是否工作线程数达到最大线程数
        2.2.1 未达到
            创建并开启新的工作线程
        2.2.2 达到
            拒绝任务

如何保证核心的线程不会退出
先看一段源码,出自ThreadPoolExecutor.getTask()方法

boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

if ((wc > maximumPoolSize || (timed && timedOut))
    && (wc > 1 || workQueue.isEmpty())) {
    if (compareAndDecrementWorkerCount(c))
        return null;
    continue;
}

try {
    Runnable r = timed ?
        workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
        workQueue.take();
    if (r != null)
        return r;
    timedOut = true;
} catch (InterruptedException retry) {
    timedOut = false;
}

上面的源码大概意思就是如果timed为true即设置了allowCoreThreadTimeOut为true 或者 当前线程数大于核心线程数,那么执行队列的poll方法并设置了超时时间,也就是我们常说的空闲线程在多久后会自动释放的原因,那如果不是上面两种情况就会调用队列的take方法一直阻塞等待获取任务,这也就解释了如何保持核心线程数不被回收的原因。

上一篇 下一篇

猜你喜欢

热点阅读