美文网首页
开线程的方式

开线程的方式

作者: 晚上吃什么啊 | 来源:发表于2019-11-24 00:15 被阅读0次

    首先何为线程(进程/线程)?
    进程操作系统动态执行基本单元,系统为每个进程分配内存,包括一般情况下,包括文本区域(text region/指令)、数据区域(data region/变量)和堆栈(stack region/对象)。

    我们的程序虽然可以做到多进程,但是,多进程需要切换上下文,什么是上下文?
    当程序执行到多进程的指令,那么会把当前的运行环境-堆栈等,复制一份,到另一块内存区域,而又因为cpu是轮寻机制,关于CPU运行原理,百度上有篇文章写的很好,我这边引用一下。
    频繁切换上下文(大概一次20微秒),属于没必要的消耗。另外就是进程间通信需要通过管道机制,比较复杂。

    所以通常需要性能的场景通常合理使用线程来提高效率,线程的定义是,一个线程有且至少有一个线程,线程利用进程的资源,且本身不拥有系统资源,所以对线程的调度的开销就会小很多(相对进程)。

    因为这篇文章我定义到Java的分类下面,所以还是要通过Java来描述
    其实我认为要真的好好深入学习线程进程,cpu调度这块,还是要通过C来学
    日后有时间,我会用C语言来模拟实现一遍

    既然了解了什么是线程,看下Java怎么实现多线程:
    Thread,Runnable,Future
    至于网上有些说4种的,其实就是用ExecutorService来管理了一下。
    那么从头聊一聊。

    Thread 其实是Runnable的实现类,类声明如下

    public class Thread implements Runnable
    

    看下最核心的一个方法
    首先判定现成的状态,0状态表示该线程是新创建的,一切不是新建状态的线程,都视为非法
    第二部添加到线程组,线程组默认初始长度为4,如果满了就阔为2倍。
    之后可以看到,调用了一个本地方法start0,如果成功,则更改started标签量
    最后一个判定,启动失败,从线程组中移除当前线程。

    public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
    
        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);
    
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
    

    Thread中出现的Runnable,作为一个接口,只有一个方法,就是run

    @FunctionalInterface
    public interface Runnable {
        /**
         * When an object implementing interface <code>Runnable</code> is used
         * to create a thread, starting the thread causes the object's
         * <code>run</code> method to be called in that separately executing
         * thread.
         * <p>
         * The general contract of the method <code>run</code> is that it may
         * take any action whatsoever.
         *
         * @see     java.lang.Thread#run()
         */
        public abstract void run();
    }
    

    之后来看Future,
    Future其实提供了和Runnable能力并列的接口,简单解释一下为什么这么说,Runnable接口提供了run,也就是可以放在线程中执行的能力,Future其实是赋予了线程执行后可以返回的能力,run的声明是void,所以没有返回值。

    两者结合,简单易懂的一个类RunnableFuture的接口就出来了。
    那么相当于Thread的实现类,FutureTask就出现了,它就是集大成者。

    这么说可能有点跳跃,先看下下面的实现,一看,诶,怎么没有Future?
    Future本身是一个接口,跟Runnable是相同的级别,但区别通俗来讲在于他没有run的能力,这个能力来自于Runnable。
    追溯一下FutureTask,发现它继承了RunnableFuture,诶,这个单词起的就有意思了,包含了Runnable,和Future。
    点进去看下

    public interface RunnableFuture<V> extends Runnable, Future<V> {
        /**
         * Sets this Future to the result of its computation
         * unless it has been cancelled.
         */
        void run();
    }
    

    Future就在这,关于Future里面有什么,大家可以点进去看看,里面最关键的就是get() throws InterruptedException, ExecutionException;这个方法,就这这个方法,让我们通过调用api,拿到线程里面的值。

    如果想使用这个东西,开启线程,这个时候不能用new Thread(future)这种方式了,因为Thread没有这种能力,只实现了一个Runnable接口,
    这个时候,一个新的类出现了,源码如下

    @FunctionalInterface
    public interface Callable<V> {
        /**
         * Computes a result, or throws an exception if unable to do so.
         *
         * @return computed result
         * @throws Exception if unable to compute a result
         */
        V call() throws Exception;
    }
    

    看下Callable的声明,是有返回值的,并且可以抛出异常的。
    这个返回值就很关键了,通过这个返回值,你可以把任何你想通过线程拿到的结果拿回来。
    而拿结果的方法就是FutureTask的get()方法,之前我们看源码时又看到,这个get方法来自于Future接口的V get()方法

    简单看下如何使用一个有返回值的多线程操作

    public class CallableTest {
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            Callable<Integer> callable = new Task();
            FutureTask task = new FutureTask(callable);
    
            Thread oneThread = new Thread(task);
            oneThread.start();
    
            System.out.println(">>>  工作结果 " + task.get().toString());
        }
    
    }
    
    class Task implements Callable<Integer> {
    
        @Override
        public Integer call() throws Exception {
            System.out.println(">>>  线程开始工作");
            Thread.sleep(1000);
            System.out.println(">>>  结束工作开始返回");
            return 10;
        }
    
    }
    

    可以看到FutureTask依然调用的是Thread,走的是本地方法start0
    Runbale就没什么好说的了,实现一个接口,放到Thread里面去执行,基本没什么东西,能力与Thread差不多,区别是实现Runnable接口的类必须依托于Thread类才能启动,

    //使用这个构造方法
    public Thread(Runnable target) {
        this(null, target, "Thread-" + nextThreadNum(), 0);
    }
    
    

    然后用Thread的start方法,需要注意的是,千万不要调run方法, 要用start。

    最后看下ExecutorService这个类,ExecutorService级别很高,他的爸爸直接就是Executor。
    他的儿子,是AbstractExecutorService,这里实现了submit,doInvokeAny等方法。
    而我们调用Executors.newFixedThreadPool(poolSize);返回的是ThreadPoolExecutor


    注:一般不建议使用Executors.newFixedThreadPool(poolSize);,什么东西全是默认,建议如下方式:

    ThreadFactory factory = new ThreadFactoryBuilder().setNameFormat("test-%d").build();
    ExecutorService service = new ThreadPoolExecutor(5, 200, 0L, TimeUnit.MILLISECONDS,
             new LinkedBlockingDeque<>(1024), factory, new ThreadPoolExecutor.AbortPolicy());
    

    具体参数含义就自行百度吧,很多人讲的。

    ThreadPoolExecutor这个类,又是AbstractExecutorService的儿子,所以这个关系就很明显了
    ThreadPoolExecutor -> AbstractExecutorService -> ExecutorService -> Executor

    看到一堆submit方法,然而没什么用,真正关键的方法,是execute方法,在ThreadPoolExecutor中实现。

    这个类有点意思,一上来就给我一个下马威
    private final AtomicInteger ctl = new AtlmicInteger(ctlOf(RUNNING, 0));

    这个ctl到底是什么?
    查了很久,在cnblogs里找到一个大神的描述 -- Okevin

    这个变量使用来干嘛的呢?它的作用有点类似我们在《7.ReadWriteLock接口及其实现ReentrantReadWriteLock》中提到的读写锁有读、写两个同步状态,而AQS则只提供了state一个int型变量,此时将state高16位表示为读状态,低16位表示为写状态。这里的clt同样也是,它表示了两个概念:

    workerCount:当前有效的线程数
    runState:当前线程池的五种状态,Running、Shutdown、Stop、Tidying、Terminate。
      int型变量一共有32位,线程池的五种状态runState至少需要3位来表示,故workCount只能有29位,所以代码中规定线程池的有效线程数最多为2^29-1。

    看到这先来聊一下线程提交任务的规则,--《java并发编程艺术》

    1. 首先会判断核心线程池里是否有线程可执行,有空闲线程则创建一个线程来执行任务。
    2. 当核心线程池里已经没有线程可执行的时候,此时将任务丢到任务队列中去。
    3. 如果任务队列(有界)也已经满了的话,但运行的线程数小于最大线程池的数量的时候,此时将会新建一个线程用于执行任务,但如果运行的线程数已经达到最大线程池的数量的时候,此时将无法创建线程执行任务。
        所以实际上对于线程池不仅是单纯地将任务丢到线程池,线程池中有线程就执行任务,没线程就等待。

    最后附上大神对execute的注解

    /**
      * corePoolSize:核心线程池的线程数量
      * 
      * maximumPoolSize:最大的线程池线程数量
      * 
      * keepAliveTime:线程活动保持时间,线程池的工作线程空闲后,保持存活的时间。
      * 
      * unit:线程活动保持时间的单位。
      * 
      * workQueue:指定任务队列所使用的阻塞队列
    */
    //ThreadPoolExecutor#execute
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
       //由它可以获取到当前有效的线程数和线程池的状态
       /*1.获取当前正在运行线程数是否小于核心线程池,是则新创建一个线程执行任务,否则将任务放到任务队列中*/
       int c = ctl.get();
        if (workerCountOf(c) < corePoolSize){
            if (addWorker(command, tre))     //在addWorker中创建工作线程执行任务
                return ;
            c = ctl.get();
        }
        /*2.当前核心线程池中全部线程都在运行workerCountOf(c) >= corePoolSize,所以此时将线程放到任务队列中*/
        //线程池是否处于运行状态,且是否任务插入任务队列成功
        if (isRunning(c) && workQueue.offer(command))    {
            int recheck = ctl.get();
         if (!isRunning(recheck) && remove(command))//线程池是否处于运行状态,如果不是则使刚刚的任务出队
           reject(command);//抛出RejectedExceptionException异常
         else if (workerCountOf(recheck) == 0)
           addWorker(null, false);
      }
        /*3.插入队列不成功,且当前线程数数量小于最大线程池数量,此时则创建新线程执行任务,创建失败抛出异常*/
      else if (!addWorker(command, false)){
        reject(command);    //抛出RejectedExceptionException异常
      }
    }
    
    //ThreadPoolExecutor#addWorker
    private boolean addWorker(Runnable firstTask, boolean core) {
    /*首先会再次检查线程池是否处于运行状态,核心线程池中是否还有空闲线程,都满足条件过后则会调用compareAndIncrementWorkerCount先将正在运行的线程数+1,数量自增成功则跳出循环,自增失败则继续从头继续循环*/
      ...
      if (compareAndIncrementWorkerCount(c))
        break retry;
      ...
      /*正在运行的线程数自增成功后则将线程封装成工作线程Worker*/
      boolean workerStarted = false;
      boolean workerAdded = false;
      Worker w = null;
      try {
        final ReentrantLock mainLock = this.mainLock;//全局锁
        w = new Woker(firstTask);//将线程封装为Worker工作线程
        final Thread t = w.thread;
        if (t != null) {
          //获取全局锁
          mainLock.lock();
          /*当持有了全局锁的时候,还需要再次检查线程池的运行状态等*/
          try {
            int c = clt.get();
            int rs = runStateOf(c);        //线程池运行状态
            if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)){          //线程池处于运行状态,或者线程池关闭且任务线程为空
              if (t.isAlive())    //线程处于活跃状态,即线程已经开始执行或者还未死亡,正确的应线程在这里应该是还未开始执行的
                throw new IllegalThreadStateException();
              //private final HashSet<Worker> wokers = new HashSet<Worker>();
              //包含线程池中所有的工作线程,只有在获取了全局的时候才能访问它。将新构造的工作线程加入到工作线程集合中
              workers.add(w);
              int s = worker.size();    //工作线程数量
              if (s > largestPoolSize)
                largestPoolSize = s;
              workerAdded = true;    //新构造的工作线程加入成功
            }
          } finally {
            mainLock.unlock();
          }
          if (workerAdded) {
            //在被构造为Worker工作线程,且被加入到工作线程集合中后,执行线程任务
            //注意这里的start实际上执行Worker中run方法,所以接下来分析Worker的run方法
            t.start();
            workerStarted = true;
          }
        }
      } finally {
        if (!workerStarted)   //未能成功创建执行工作线程
          //在启动工作线程失败后,将工作线程从集合中移除
          addWorkerFailed(w);    
      }
      return workerStarted;
    }
    
    //ThreadPoolExecutor$Worker,它继承了AQS,同时实现了Runnable,所以它具备了这两者的所有特性
    private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
      final Thread thread;
      Runnable firstTask;
      public Worker(Runnable firstTask) {
        //设置AQS的同步状态为-1,禁止中断,直到调用runWorker
        setState(-1);    
        this.firstTask = firstTask;
        //通过线程工厂来创建一个线程,将自身作为Runnable传递传递
        this.thread = getThreadFactory().newThread(this);    
      }
      public void run() {
        runWorker(this);    //运行工作线程
      }
    }
    

    Okevin博客

    相关文章

      网友评论

          本文标题:开线程的方式

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