美文网首页
谈谈我对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线程的了解

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