这是我一个朋友在美团面试中遇到的一个问题,今日拿出来解析一下

正文

怎样打开一个线程

怎样打开一个线程,再JDK中的说明为:

/**
 * ...
 * There are two ways to create a new thread of execution. One is to
 * declare a class to be a subclass of <code>Thread</code>. 
 * The other way to create a thread is to declare a class that
 * implements the <code>Runnable</code> interface.
 * ....
 */
public class Thread implements Runnable{
   
}
​

Thread源码的类描绘中有这样一段,翻译一下,只有两种方法去创建一个实行线程,一种是声明一个Thread的子类,另一种是创建一个类去完结Runnable接口。

继承Thread类
public class ThreadUnitTest {
​
  @Test
  public void testThread() {
    //创建MyThread实例
    MyThread myThread = new MyThread();
    //调用线程start的方法,进入可实行状况
    myThread.start();
   }
​
  //继承Thread类,重写内部run方法
  static class MyThread extends Thread {
​
    @Override
    public void run() {
      System.out.println("test MyThread run");
     }
   }
}
完结Runnable接口
public class ThreadUnitTest {
​
  @Test
  public void testRunnable() {
    //创建MyRunnable实例,这其实仅仅一个任务,并不是线程
    MyRunnable myRunnable = new MyRunnable();
    //交给线程去实行
    new Thread(myRunnable).start();
   }
​
  //完结Runnable接口,并完结内部run方法
  static class MyRunnable implements Runnable {
​
    @Override
    public void run() {
      System.out.println("test MyRunnable run");
     }
   }
}
完结Callable

其实完结Callback接口创建线程的方法,归根结底就是Runnable方法,只不过它是在Runnable的基础上又增加了一些才干,例如取消任务实行等。

public class ThreadUnitTest {
​
  @Test
  public void testCallable() {
    //创建MyCallable实例,需求与FutureTask结合运用
    MyCallable myCallable = new MyCallable();
    //创建FutureTask,与Runnable相同,也只能算是个任务
    FutureTask<String> futureTask = new FutureTask<>(myCallable);
    //交给线程去实行
    new Thread(futureTask).start();
​
    try {
      //get方法获取任务返回值,该方法是阻塞的
      String result = futureTask.get();
      System.out.println(result);
     } catch (ExecutionException e) {
      e.printStackTrace();
     } catch (InterruptedException e) {
      e.printStackTrace();
     }
   }
​
  //完结Callable接口,并完结call方法,不同之处是该方法有返回值
  static class MyCallable implements Callable<String> {
​
    @Override
    public String call() throws Exception {
      Thread.sleep(10000);
      return "test MyCallable run";
     }
   }
}

Callable的方法有必要与FutureTask结合运用,我们看看FutureTask的继承联络:

//FutureTask完结了RunnableFuture接口
public class FutureTask<V> implements RunnableFuture<V> {
​
}
​
//RunnableFuture接口继承Runnable和Future接口
public interface RunnableFuture<V> extends Runnable, Future<V> {
  void run();
}

打开很多线程会引起什么问题

在Java中,调用Thread的start方法后,该线程即置为安排妥当状况,等候CPU的调度。这个流程里有两个重角度需求去了解。

start内部怎样打开线程的?看看start方法是怎样完结的。

// Thread类的start方法
public synchronized void start() {
    // 一系列状况检查
    if (threadStatus != 0)
      throw new IllegalThreadStateException();
  
    group.add(this);
      
    boolean started = false;
    try {
       //调用start0方法,真实发动java线程的地方
      start0();
      started = true;
     } finally {
      try {
        if (!started) {
         group.threadStartFailed(this);
         }
       } catch (Throwable ignore) {
       }
     }
   }
  
//start0方法是一个native方法
private native void start0();

JVM中,native方法与java方法存在一个映射联络,Java中的start0对应c层的JVM_StartThread方法,我们持续看一下:

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
 JVMWrapper("JVM_StartThread");
 JavaThread *native_thread = NULL;
 bool throw_illegal_thread_state = false;
  {
  
  MutexLocker mu(Threads_lock);
  // 判别Java线程是否现已发动,假设现已发动过,则会抛失常。
  if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
   throw_illegal_thread_state = true;
   } else {
   //假设没有发动过,走到这儿else分支,去创建线程
   //分配c++线程结构并创建native线程
   jlong size =
       java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
  
   size_t sz = size > 0 ? (size_t) size : 0;
   //留心这儿new JavaThread
   native_thread = new JavaThread(&thread_entry, sz);
   if (native_thread->osthread() != NULL) {
    native_thread->prepare(jthread);
    }
   }
  }
  ......
 Thread::start(native_thread);

走到这儿发现,Java层现已过渡到native层,但远远还没结束:

JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
             Thread()
  {
   initialize();
   _jni_attach_state = _not_attaching_via_jni;
   set_entry_point(entry_point);
   os::ThreadType thr_type = os::java_thread;
   thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :
                            os::java_thread;
   //根据渠道,调用create_thread,创建真实的内核线程            
   os::create_thread(this, thr_type, stack_sz);
  }
  
  bool os::create_thread(Thread* thread, ThreadType thr_type,
             size_t req_stack_size) {
    ......
    pthread_t tid;
    //运用pthread_create()来创建线程
    int ret = pthread_create(&tid, &attr, (void* (*)(void*)) thread_native_entry, thread);
    ......
    return true;
}

pthread_create方法,第三个参数表明发动这个线程后要实行的方法的进口,第四个参数表明要给这个方法传入的参数:

static void *thread_native_entry(Thread *thread) {
  ......
 //thread_native_entry方法的最下面的run方法,这个thread就是上面传递下来的参数,也就是JavaThread
 thread->run();
  ......
 return 0;
}

终于开始实行run方法了:

//thread.cpp类
void JavaThread::run() {
  ......
 //调用内部thread_main_inner 
 thread_main_inner();
}
  
void JavaThread::thread_main_inner() {
 if (!this->has_pending_exception() &&
  !java_lang_Thread::is_stillborn(this->threadObj())) {
   {
   ResourceMark rm(this);
   this->set_native_thread_name(this->get_thread_name());
   }
  HandleMark hm(this);
  //留心:内部通过JavaCalls模块,调用了Java线程要实行的run方法
  this->entry_point()(this, this);
  }
 DTRACE_THREAD_PROBE(stop, this);
 this->exit(false);
 delete this;
}

一条U字型代码调用链至此结束:

  • Java中调用Thread的star方法,通过JNI方法,调用到native层。
  • native层,JVM通过pthread_create方法创建一个系统内核线程,并指定内核线程的初始工作地址,即一个方法指针。
  • 在内核线程的初始工作方法中,运用JavaCalls模块,回调到java线程的run方法,开始java等级的线程实行。
线程怎样调度

核算机的世界里,CPU会分为若干时间片,通过各种算法分配时间片来实行任务,有耳熟能详时间片轮转调度算法、短进程优先算法、优先级算法等。当一个任务的时间片用完,就会切换到另一个任务。在切换之前会保存上一个任务的状况,当下次再切换到该任务,就会加载这个状况, 这就是所谓的线程的上下文切换。很明显,上下文的切换是有开支的,包括许多方面,操作系统保存和恢复上下文的开支、线程调度器调度线程的开支和高速缓存从头加载的开支等。

怎样敞开一个线程,敞开很多线程会有什么问题,怎样优化?(美团面试问道)

通过上面两个理论基础的回顾,打开很多线程引起的问题,总结起来,就两个字——开支。

消耗时间:线程的创建和销毁都需求时间,当数量太大的时分,会影响功率。 消耗内存:创建更多的线程会消耗更多的内存,这是毋庸置疑的。线程一再创建与销毁,还有或许引起内存抖动,一再触发GC,最直接的体现就是卡顿。长而久之,内存资源占用过多或许内存碎片过多,系统甚至会出现OOM。 消耗CPU。在操作系统中,CPU都是遵从时间片轮转机制进行处理任务,线程数过多,必定会引起CPU一再的进行线程上下文切换。这个价值是贵重的,某些场景下甚至逾越任务自身的消耗。

怎样优化

线程的本质是为了实行任务,在核算机的世界里,任务分大致分为两类,CPU密集型任务和IO密集型任务。

CPU密集型任务,比如公式核算、资源解码等。这类任务要进行很多的核算,全都依赖CPU的运算才干,耐久消耗CPU资源。所以针对这类任务,其实不应该打开很多线程。由于线程越多,花在线程切换的时间就越多,CPU实行功率就越低,一般CPU密集型任务一起进行的数量等于CPU的中心数,最多再加个1。 IO密集型任务,比如网络读写、文件读写等。这类任务不需求消耗太多的CPU资源,绝大部分时间是在IO操作上。所以针对这类任务,可以打开很多线程去进步CPU的实行功率,一般IO密集型任务一起进行的数量等于CPU的中心数的两倍。 其他,在无法防止,必需求打开很多线程的情况下,我们也可以运用线程池替代直接创建线程的做法进行优化。线程池的基本效果就是复用已有的线程,然后减少线程的创建,降低开支。在Java中,线程池的运用仍是十分方便的,JDK中供应了现成的ThreadPoolExecutor类,我们只需求依照自己的需求进行相应的参数配置即可,这儿供应一个示例。

/**
 * 线程池运用
 */
public class ThreadPoolService {
​
  /**
   * 线程池变量
   */
  private ThreadPoolExecutor mThreadPoolExecutor;
​
  private static volatile ThreadPoolService sInstance = null;
​
  /**
   * 线程池中的中心线程数,默许情况下,中心线程一向存活在线程池中,即使他们在线程池中处于放置状况。
   * 除非我们将ThreadPoolExecutor的allowCoreThreadTimeOut特色设为true的时分,这时分处于放置的中心     * 线程在等候新任务到来时会有超时战略,这个超时时间由keepAliveTime来指定。一旦逾越所设置的超时时间,闲   * 置的中心线程就会被停止。
   * CPU密集型任务  N+1  IO密集型任务  2*N
   */
  private final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() + 1;
  /**
   * 线程池中所容纳的最大线程数,假设活动的线程抵达这个数值往后,后续的新任务将会被阻塞。包括中心线程数+非*    * 中心线程数。
   */
  private final int MAXIMUM_POOL_SIZE = Math.max(CORE_POOL_SIZE, 10);
  /**
   * 非中心线程放置时的超时时长,对于非中心线程,放置时间逾越这个时间,非中心线程就会被回收。
   * 只有对ThreadPoolExecutor的allowCoreThreadTimeOut特色设为true的时分,这个超时时间才会对中心线    * 程发生效果。
   */
  private final long KEEP_ALIVE_TIME = 2;
  /**
   * 用于指定keepAliveTime参数的时间单位。
   */
  private final TimeUnit UNIT = TimeUnit.SECONDS;
  /**
   * 线程池中保存等候实行的任务的阻塞队伍
   * ArrayBlockingQueue  根据数组完结的有界的阻塞队伍
   * LinkedBlockingQueue  根据链表完结的阻塞队伍
   * SynchronousQueue  内部没有任何容量的阻塞队伍。在它内部没有任何的缓存空间
   * PriorityBlockingQueue  具有优先级的无限阻塞队伍。
   */
  private final BlockingQueue<Runnable> WORK_QUEUE = new LinkedBlockingDeque<>();
  /**
   * 线程工厂,为线程池供应新线程的创建。ThreadFactory是一个接口,里边只有一个newThread方法。 默许为DefaultThreadFactory类。
   */
  private final ThreadFactory THREAD_FACTORY = Executors.defaultThreadFactory();
  /**
   * 拒绝战略,当任务队伍已满并且线程池中的活动线程现已抵达所限定的最大值或许是无法成功实行任务,这时分    * ThreadPoolExecutor会调用RejectedExecutionHandler中的rejectedExecution方法。
   * CallerRunsPolicy  只用调用者地点线程来工作任务。
   * AbortPolicy  直接抛出RejectedExecutionException失常。
   * DiscardPolicy  丢掉掉该任务,不进行处理。
   * DiscardOldestPolicy  丢掉队伍里最近的一个任务,并实行当前任务。
   */
  private final RejectedExecutionHandler REJECTED_HANDLER = new ThreadPoolExecutor.AbortPolicy();
​
  private ThreadPoolService() {
   }
​
  /**
   * 单例
   * @return
   */
  public static ThreadPoolService getInstance() {
    if (sInstance == null) {
      synchronized (ThreadPoolService.class) {
        if (sInstance == null) {
          sInstance = new ThreadPoolService();
          sInstance.initThreadPool();
         }
       }
     }
    return sInstance;
   }
​
  /**
   * 初始化线程池
   */
  private void initThreadPool() {
    try {
      mThreadPoolExecutor = new ThreadPoolExecutor(
          CORE_POOL_SIZE,
          MAXIMUM_POOL_SIZE,
          KEEP_ALIVE_TIME,
          UNIT,
          WORK_QUEUE,
          THREAD_FACTORY,
          REJECTED_HANDLER);
     } catch (Exception e) {
      LogUtil.printStackTrace(e);
     }
   }
​
  /**
   * 向线程池提交任务,无返回值
   *
   * @param runnable
   */
  public void post(Runnable runnable) {
    mThreadPoolExecutor.execute(runnable);
   }
​
  /**
   * 向线程池提交任务,有返回值
   *
   * @param callable
   */
  public <T> Future<T> post(Callable<T> callable) {
    RunnableFuture<T> task = new FutureTask<T>(callable);
    mThreadPoolExecutor.execute(task);
    return task;
   }
}

今日共享到此结束,对你有协助的话,点个赞再走呗,每日一个面试小技巧

注重大众号:Android老皮
解锁 《Android十大板块文档》 ,让学习更贴近未来实战。已构成PDF版

内容如下

1.Android车载应用开发系统学习指南(附项目实战)
2.Android Framework学习指南,助力成为系统级开发高手
3.2023最新Android中高档面试题汇总+解析,离别零offer
4.企业级Android音视频开发学习路途+项目实战(附源码)
5.Android Jetpack从入门到知晓,构建高质量UI界面
6.Flutter技术解析与实战,跨渠道首要之选
7.Kotlin从入门到实战,全方面提升架构基础
8.高档Android插件化与组件化(含实战教程和源码)
9.Android 功用优化实战+360全方面功用调优
10.Android零基础入门到知晓,高手进阶之路

敲代码不易,注重一下吧。ღ( ・ᴗ・` )