OSProcess,线程进程:程序在执行期间,包含其他资源,如打开文件,挂起信号,内核内部数据,处理器状态,内核地址空间,一个或多个线程执行,数据段。线程:进程中的活动对象。内核调度的对象不是进程而是线程;传统Unix系统中的一个进程只包含一个线程。Linux中线程的实现从Linux内核的角度来看,并没有线程的概念。Linux把所有的线程都作为进程来实现,内核并没有为线程准备特殊的调度算法和特殊的数据结构。线程简单地被认为是一个与其他进程共享一些资源的进程。因此,从内核的角度来看,它是一个普通的进程。Windows或Solaris等操作系统的实现中,都提供了专门支持线程的机制(轻量级进程)。Copy-on-write传统的fork()系统调用直接将所有资源复制到新创建的进程中,效率非常低,因为复制的数据可能并不需要。Linux的fork()是使用写时复制实现的。内核此时并不复制整个进程地址空间,而是让父进程和子进程共享一份。数据只有在需要写入时才会被复制,而在此之前,它只是以只读方式共享。这种优化可以避免复制大量根本不会使用的数据(地址空间通常包含数十兆字节的数据)。因此,Linux创建的进程和线程的区别在于共享地址空间、文件系统资源、文件描述符、信号处理程序等方面的区别。以下是StackOverflow上的一个回答:即在Linux下,进程是使用fork()创建的,线程是使用pthread_create()创建的;fork()和pthread_create()都是通过clone()函数实现的,只是传递的参数不同。即共享资源不同。(Linux通过NPTL实现POSIXThread规范,即通过轻量级进程实现POSIXThread,使Unix上的库和软件顺利迁移到Linux上)Java线程如何映射到OS线程JVM是在Linux平台创建的线程,需要使用pthread接口。pthread是POSIX标准的一部分,该标准定义了用于创建和管理线程的C语言接口。Linux提供了pthread的实现:pthread_ttid;if(pthread_create(&tid,&attr,thread_entry_point,arg_to_entrypoint)){fprintf(stderr,"Errorcreatingthread\n");return;}tid是新创建线程的IDattr,就是我们需要设置的线程属性thread_entry_point是新创建线程会调用的函数指针。arg_to_entrypoint是参数thread_entry_point指向的函数,thread_entry_point是Thread对象的run方法。无返回值线程和有返回值线程无返回值:一种是直接继承Thread,另一种是实现有返回值的Runnable接口:通过Callable和Future实现有返回值的线程在实践中比较常用。竞争条件当计算的正确性取决于多个线程交替执行的时间时,就会出现竞争条件。最常见的竞争条件类型是“Check-Then-Act”操作,它使用可能无效的观察来确定下一步操作。“执行前检查”的一个常见用法是延迟初始化:publicclassLazyInitRace{privateExpensiveObjectinstance=null;publicExpensiveObjectgetInstance(){if(instance==null){instance=newExpensiveObject();}返回实例;}}不要这样做。Executor框架使用裸线程的缺点在prod环境下,每个任务分配一个线程的方式存在严重缺陷,尤其是需要创建大量线程时:线程生命周期的开销非常高:创建线程的销毁不是免费的。资源消耗:会消耗内存和CPU,大量线程争抢CPU资源会造成性能开销。如果您已经有足够的线程来保持所有CPU忙碌,那么创建更多线程反而会降低性能。稳定性:对可以创建的线程数有限制,包括JVM启动参数和操作系统对线程的限制。如果超出这些限制,可能会抛出OutOfMemoryError异常。Executor的基本原理Executor是基于生产者-消费者模型的。提交任务的操作相当于生产者,执行任务的线程相当于消费者。线程池的构造函数如下:publicThreadPoolExecutor(intcorePoolSize,intmaximumPoolSize,longkeepAliveTime,TimeUnitunit,BlockingQueueworkQueue,RejectedExecutionHandlerhandler){this(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,faacut)handler);}线程池大小corePoolSize:核心线程数,当线程池中的线程数小于corePoolSize时,直接创建新线程。队列的末尾;如果此时任务队列已满,则会创建一个新的线程来执行这个任务。线程数等于maximumPoolSize:如果任务队列未满,则将此任务插入到任务队列的尾部;如果此时任务队列已满,则由RejectedExecutionHandler处理。keep-alivekeepAliveTime:当我们线程池中的线程数大于corePoolSize时,如果一个线程空闲(Idle)的时间超过了指定的时间(keepAliveTime),线程池就会销毁该线程。工作队列工作队列(WorkQueue)是一个BlockingQueue,用于存放已经提交但没有空闲线程执行的任务。有几种常见的工作队列:直接切换、无界队列和有界队列。在生产环境中,禁止使用无界队列,因为当队列中堆积的任务过多时,会消耗大量内存,最后OOM;通常设置一个固定大小的有界队列。当线程池满了,队列也满了,直接拒绝新提交的任务,抛出RejectedExecutionException。本质上,这是对服务本身的一种威胁保护机制,当服务没有资源处理新提交的任务时,会直接拒绝。Java原生线程池在生产环境的问题在服务后台,我们的框架一般会集成全链路跟踪的功能,把整个调用链连接起来,主要记录TraceId和SpanId;TraceId和SpanId一般记录在ThreadLocal中,对业务方是透明的。在同一个线程中进行同步RPC调用时,不会有问题;但是如果我们使用线程池来进行客户端异步调用,就会造成Trace信息的丢失。根本原因是Trace信息无法从主线程的ThreadLocal传过来。到线程池的ThreadLocal。针对这个痛点,阿里巴巴开源的transmittable-thread-local解决了这个问题,实现起来并不难。您可以阅读源代码:https://github.com/alibaba/transmittable-thread-localPerformanceandScalabilityReflectionsonPerformance提高性能意味着用更少的资源做更多的事情。“资源”可以指很多,比如CPU时钟周期、内存、网络带宽、磁盘空间等资源。当一个操作的性能因特定资源而受到限制时,我们通常称该操作为资源密集型操作,例如CPU密集型、IO密集型等。使用多线程理论上可以提升服务的整体性能,但是与单线程相比,使用多线程会引入额外的性能开销。包括:线程间的协调(如加锁、触发信号、内存同步)、增加上下文切换、线程创建和销毁、线程调度。如果过度使用线程,其性能可能比实现相同功能的串行程序还要差。从性能监控的角度来看,CPU需要尽可能保持忙碌。如果程序是计算密集型的,可以通过添加处理器来提高性能。但是如果程序不能让CPU忙碌,那么增加更多的处理器也无济于事。Scalability可扩展性是指:当计算资源(如CPU、内存、存储容量、IO带宽)增加时,程序的吞吐量或处理能力可以相应增加。我们熟悉的三层模型,即程序中的表现层、业务逻辑层、持久层相互独立,可能由不同的服务处理,很好地说明了性能损失提高可扩展性通常会导致。如果将表现层、业务逻辑层、持久层都集成到一个应用中,在负载不高的情况下,其性能肯定会比将应用分成多个层更高。这种单体应用避免了在不同层之间传输任务时存在的网络延迟,并减少了很多开销。但是,当单个应用达到自身处理能力的极限时,就会遇到一个严重的问题:处理能力的提升非常困难,即无法横向扩展。阿姆达尔定律大多数并发程序由一系列并行作业和串行作业组成。阿姆达尔定律描述:在计算资源增加的情况下,程序理论上可以达到最高的加速比,而这个值取决于程序中并行组件和串行组件的比例。假设F是必须串行执行的部分,那么根据Amdahl定律,在包含N个处理器的机器上,最高加速比为:当N接近无穷大时,最大加速比接近1/F。因此,如果程序中50%的计算需要串行执行,那么最高加速比只能是2。上下文切换线程调度会引起上下文切换,上下文切换会产生开销。如果一个CPU密集型程序产生大量的线程切换,会降低系统的吞吐量。UNIX系统的vmstat命令可以报告内核中上下文切换次数、执行时间所占比例等信息。高核心占用率(超过10%)通常表示高调度活动,很可能是由于I/O或锁争用引起的阻塞。>>vmstatprocs----------内存--------------swap-------io-----system--------cpu-----rbswpdfreebuffcachesisobiboincsussyidwast100323593223825632027760001174109900cs:每秒上下文切换次数sy:kernel系统进程执行时间百分比us:上面的用户进程执行时间百分比。原文链接https://segmentfault.com/a/11...