多线程

      

   进程(Process):应用程序的执行实例,有独立的内存空间和系统资源

      进程具有的特征:

      • 动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的;
      • 并发性:任何进程都可以同其他进程一起并发执行;
      • 独立性:进程是系统进行资源分配和调度的一个独立单位;
      • 结构性:进程由程序、数据和进程控制块三部分组成。

   线程(Thread):cpu调度和分派的基本单位,进程中执行运算的最小单位,可完成一个独立的顺序控制流程 

   协程(Coroutines):基于线程之上,但比线程更加轻量级,由程序员自己写程序来管理的轻量级线程叫做[用户空间线程],具有对内核来说不可见的特性。

            因为是自主开辟的异步任务,所以很多人也更喜欢叫它们纤程(Fiber),或者绿色线程(GreenThread)。正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。

       协程的特点:

         1.线程的切换由操作系统负责调度,协程由用户自己进行调度,因此减少了上下文切换,提高了效率。

         2.线程的默认Stack大小是1M,而协程更轻量,接近1K。因此可以在相同的内存中开启更多的协程。

         3.由于在同一个线程上,因此可以避免竞争关系而使用锁。

         4.适用于被阻塞,且需要大量并发的场景。但不适用于大量计算的多线程,遇到此种情况,更好实用线程去解决。

 

   进程和线程的区别

        1.线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位

        2.一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线

        3.进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开的文件和信号等),某进程内的线程在其他进程不可见

        4.调度和切换:线程上下文切换比进程上下文切换要快的多

      线程和进程都是一种抽象的概念,线程是一种比进程还小的抽象,线程和进程都可用于实现并发。

      在早期操作系统里并没有线程的概念,进程是能拥有资源和独立运行的最小单位,也是程序执行的最小单位,它相当于一个进程里只有一个线程,进程本身就是线程。所以线程有是被称为轻量级进程

      后来,随着计算机的发展,对多个任务之间上下文切换的效率要求越来越高,就抽象出一个更小的概念-线程,一般一个进程会有多个(也可以是一个)线程。

 

   协程和线程的比较

 

比较项线程协程
占用资源 初始单位为1MB,固定不可变 初始一般为 2KB,可随需要而增大
调度所属 由 OS 的内核完成 由用户完成
切换开销 涉及模式切换(从用户态切换到内核态)、16个寄存器、PC、SP...等寄存器的刷新等 只有三个寄存器的值修改 - PC / SP / DX.
性能问题 资源占用太高,频繁创建销毁会带来严重的性能问题 资源占用小,不会带来严重的性能问题
数据同步 需要用锁等机制确保数据的一直性和可见性 不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

 

    任务调度

     大部分系统的任务调度都是采用时间轮转的抢占式调度方式,也就是说一个任务执行一小段时间后强制暂停去执行下一个任务,每个任务轮流执行。任务执行的一小段时间叫做时间片,任务正在执行时的状态叫运行状态,任务执行一段时间后强制暂停去执行下一个任务,被暂停的任务就处于就绪状态,等待下一个属于它的时间片的到来。这样每个任务都能得到执行,由于CPU的执行效率非常高,时间片非常短,在各个任务之间快速地切换,给人的感觉就是多个任务在“同时进行”,这也就是我们所说的并发

 

     并发与并行

     “如果一个系统能够同时支持两个或多个进行中操作,则称该系统具有并发性。 如果系统可以支持同时执行的两个或多个操作,则称该系统为并行系统。”

     支持多个应用同时进行中,说明它具有并发性。而要实现多个操作同时执行,需要多核CPU的硬件支持。

           并发:同一部分(CPU单核或某一个核)的系统资源多个应用交替占用                    并行:多部分(CPU多核)的系统资源多个应用各自占用

 

 

多线程

     线程的生命周期

         新建状态                                                                                                                                           

               |                                                              新建状态: 使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保存这个状态直到程序  start()  这个

       执行start方法                                                线程。 

          |                                                      

        就绪状态------------------|                                     就绪状态:当线程对象调用了 start() 方法后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

               |                            |                                     阻塞状态:如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间

                        执行run方法          阻塞状态[线程阻塞,让出系统CPU资源]                                     已到或获得设备后可以重新进入就绪状态。可以分为三种:①等待阻塞:运行状态中的线程执行wait()方法,使线程进入等待阻塞状态

          |          |                                        ②同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。

        运行状态  __________|                                        ③其他阻塞:通过调用线程的 sleep() 或 join() 发出 I/O请求时,线程就会进入到阻塞状态。当sleep()状态超时,join()等待线程终止或

         |                                                  超时,或者I/O处理完毕,线程重新转入就绪状态

      run方法执行完成                                           运行状态:如果就绪状态的线程获取CPU资源,就可以执行run(),此时便处于运行状态。它可以变为阻塞状态、就绪状态和死亡状态。

         |                                             死亡状态:一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切到终止状态。 

        死亡状态[可用 stop 与destroy 函数强制终止]                                                   

                    

     线程的优先级

         取值范围 1 (Thread.MIN_PRIORITY)   -   10(Thread.MAX_PRIORITY)  

        默认值 5 (NORM_PRIORITY)

        高优先级的线程相对于程序更加重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。

     创建线程

       ①通过实现 Runnable 接口            ②通过继承 Thread 类本身             ③通过 Callable 和 Future 创建线程   Callable弥补了Runnable无法返回值的缺点  Future主要针对异步状况  FutureTask同时实现Future和Runnable接口,   在jdk1.5中作为泛型接口添加,在jdk1.8中被声明为函数式接口(函数式接口是只定义了一个抽象方法的接口)  

      ③

        实现Callable接口,实现 call() 方法将作为线程执行体,可以有返回值。

        实例化Callable实现类,使用 FutureTask 类来包装Callable对象,该FutureTask 对象封装了该Callable对象 call() 方法的返回值。

        使用FutrueTask对象作为Thread对象的target创建并启动新线程。

        调用FutrueTask对象的get()方法来获得子线程执行接受后的返回值。

      

     继承Thread类

    子类继承Thread类具备多线程能力

    启动线程:子类对象.start

    不建议使用:避免oop单继承局限性(Object Oriented Programming 面向对象编程)

   实现Runnable接口

    实现接口Runnable具有多线程能力

    启动线程:传入目标对象 + Thread对象.start()

    推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用  (多个线程跑同一个对象)

 

     线程池

 

      1、线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。      

      2、那么,我们为什么需要用到线程池呢?每次用的时候手动创建不行吗?

      在java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程 也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。为了防止资源不足,需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务。(为什么)

线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快;另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。(什么用)

      3、线程池都是通过线程池工厂创建,再调用线程池中的方法获取线程,再通过线程去执行任务方法。

        Executors:线程池创建工厂类

        public static ExecutorServicenewFixedThreadPool(int nThreads):返回线程池对象

        ExecutorService:线程池类

        Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行线程中的call()方法

        Future 接口:用记录线程任务执行完毕后产生的结果。线程池创建与使用

      线程死锁问题产生4个条件

        互斥使用,即当资源被一个线程占用时,别的线程不能使用

        ②不可抢占,资源请求者不能强制从资源占有者手里夺取资源

        ③请求和保持,即当资源请求者在请求其他资源的同时保存对原有资源的占有

        ④循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源。这样就形成了等待环路

      ArrayList 的add()方法的非线程安全的集合(可能出现数据不一致情况)

      HashTable 的put()方法是线程安全的,但效率较低

      HashMap 的put()方法是非线程安全的,但效率较高

 

        

 

posted @ 2022-08-09 22:12  On1on  阅读(52)  评论(0)    收藏  举报