美文网首页
谈谈我对Java线程的了解

谈谈我对Java线程的了解

作者: 雪狼_lykos | 来源:发表于2020-02-21 21:01 被阅读0次
世界那么大,我想带你去走走

本篇文章主要介绍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区别

  • Runnable是一个接口,有且仅有一个run方法。而Thread是一个类,他不仅实现了Runnable而且还有自己的方法
  • 实现Runnable的类无法直接开启新的线程运行,必须要利用new Thread(runnable).start()开启新的线程

守护线程与非守护线程

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)
接下来我们分别分析一下这些线程状态
  • NEW
    当程序使用关键字的new出来的时候,这个线程就处于NEW状态,此时他与普通的java对象没有区别
Thread thread = new Thread(()->{while (true){}});
System.out.println(thread.getState());
----> 运行结果: NEW
  • Runnable
    其实我们都知道,在实际线程生命周期中还存在就绪运行的状态,但是jvm 并没有定义这两个状态,而是将这两个状态都认为是Rnunable状态,因为运行状态实在太短暂了,即时我们当下用api拿到了线程状态为运行那也并不代表此时就是运行状态。
Thread thread = new Thread(()->{while (true){}});
thread.start();
System.out.println(thread.getState());
----> 运行结果: RUNNABLE
  • BLOCKED
    当线程正在等待获取监视锁时,会进行阻塞状态
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
相关线程栈信息
  • WAITING
    等待线程的线程状态,在调用以下方法时会处于此状态
    • Object.wait with no timeout
    • Thread.join with no timeout
    • LockSupport.park

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()的线程状态
  • TIMED_WAITING
    指定等待时间的等待线程状态,执行以下方法会进入定时等待状态
    • Thread.sleep
    • Object.wait with timeout
    • Thread.join with timeout
    • LockSupport.parkNanos
    • LockSupport.parkUntil

由于在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 指定时间内的线程状态
  • TERMINATED
    线程终止状态。线程执行完成,或者异常退出。
    举个栗子:
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. 线程间通讯

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

  • 共享变量
  • 共享锁与独享锁(排它锁)
  • Object对象的方法: waitnotifyjoinparkunpark

二、线程池

作用

  • 减少创建线程和切换线程所带来的系统开销
  • 控制线程的数量,防止创建过多线程,给系统带来不必要的压力。

要点

  • 如何创建线程池
  • 如何执行任务
  • 如何保证核心的线程不会退出
    接下来我们一 一为上面的疑问作出解释
    创建线程池
    我们来看官网对ThreadPoolExecutor构造函数的定义
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方法一直阻塞等待获取任务,这也就解释了如何保持核心线程数不被回收的原因。

相关文章

  • 谈谈我对Java线程的了解

    本篇文章主要介绍java中线程和线程池的使用 一、线程 1. 线程分类 继承 java.lang.Thread 实...

  • Java 多线程 - Java 内存模型

    前言 学习Java多线程,要了解多线程可能出现的并发现象,了解Java内存模型的知识是必不可少的。 对学习到的重要...

  • Java知识梳理六

    一、Java多线程一 1.谈谈线程的生命周期和状态转移 关于线程生命周期的不同状态,在Java 5以后,线程...

  • 线程池

    Java中的多线程了解么,线程池的增长策略和拒绝策略了解么java.util.concurrent.ThreadP...

  • ThreadLocal实现原理揭秘

    ThreadLocal是什么?对java多线程有了解的人都清楚,在多个线程程序中,会出现线程安全性问题,即多线程是...

  • 线程池相关博文

    先了解了解线程Java线程基础[https://juejin.cn/post/686683499908195943...

  • Java多线程学习笔记1

    多线程学习笔记 在之前的java开发中一般都是java web方向的工作,对多线程使用的非常少。了解仅限于Runn...

  • 多线程并发 (五) ReentrantLock 使用和源码

    章节: 多线程并发 (一) 了解 Java 虚拟机 - JVM 多线程并发 (二) 了解 Thread 多线程并发...

  • java内存模型

    前言 在学习java多线程并发编程前,必须要了解java内存模型,只有了解java内存模型,才能知道为什么多线程并...

  • Java并发编程(二)同步

    如果你的java基础较弱,或者不大了解java多线程请先看这篇文章java多线程(一)线程定义、状态和属性 同步一...

网友评论

      本文标题:谈谈我对Java线程的了解

      本文链接:https://www.haomeiwen.com/subject/sfcpactx.html