美文网首页
《JAVA并发编程的艺术》笔记(review中)

《JAVA并发编程的艺术》笔记(review中)

作者: Sponge1128 | 来源:发表于2018-06-23 10:34 被阅读0次

    进程与线程

    1.进程:
           进程是资源分配的基本单位,又是调度运行的基本单位,是系统中并发执行的单位。进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。

    ① 进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括代码段、数据区和堆栈;
    ② 进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时,它才能成为一个活动的实体,我们称其为进程。

    2.线程:
           线程是进程中执行运算的最小单位,通常在一个进程中可以包含若干个线程,这若干个线程可以被同时调度到多个CPU上运行,线程可以利用进程所拥有的资源。
           多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。线程是在同一时间需要完成多项任务的时候实现的。

    线程安全问题

           在没有充足同步的情况下,由于线程内存模型及多线程中的操作执行顺序的不可预测性的缘故,对对象中的可变状态或类的共享状态执行操作时,可能会产生奇怪的结果。

    1.内存屏障

    内存屏障

    2.volatile 修饰符

           保证了共享变量的可见性,当一个线程修改一个共享变量时,另外一个线程能够读到被修改的值,不保证其原子性。
           在对volatile 变量进行写操作时会多出lock汇编代码,该指令会引发两件事情:
        1.lock前缀指令会引起处理器将缓存行写回到内存,以前的处理器通过LOCK#信号锁总线,而目前的处理器则是通过缓存锁定,锁定这块内存区域的缓存;
        2.上述写回操作会使其他CPU里缓存了该内存地址的数据无效;通过缓存一致性协议,每个处理器通过嗅探在总线上传播的数据(对共享数据的修改)来查看自己缓存的值是否过期。

    可见性:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入;
    原子性:对任意volatile变量的单个读/写操作具有原子性,但类似volatile++这种复合操作不具有原子性;

    volatile写-读的内存语义
           当写一个volatile变量时,JMM会把该线程的本地内存中的共享变量刷新到主内存;
           当读一个volatile变量时,JMM会直接从主内存中读取共享变量;
    volatile写-读的内存语义实现
       StoreStore屏障-volatile写-StoreLoad屏障;
       volatile读-LoadLoad屏障-LoadStore屏障;

    volatile重排序规则表

           只要volatile变量和普通变量之间的重排序可能会破坏volatile的内存语义,这种重排序就会被编译器重排序规则和处理器内存屏蔽插入策略禁止;

    3.synchronized

           静态同步方法锁住Class对象,普通同步方法锁住当前对象,同步代码块锁住配置的对象。JVM基于进入和退出Monitor对象来实现同步。同步代码块→monitorenter,monitorexit;同步方法→ACC_SYNCHRONIZED。任意线程对Object的同步访问,首先要获得Object的监视器,如果获取失败,线程进入同步队列,线程状态变为Blocked。当访问Object的前驱释放了锁,则该释放操作会唤醒阻塞队列中的线程,使其重新尝试对监视器的获取。
           释放锁和获取锁与volatile写-读具有相同的内存语义,在多处理器上进行CAS时,会在cmpxchg加上lock前缀。
           lock前缀:确保对内存的读-改-写操作原子执行;禁止该指令与之前和之后的读写指令重排序;把写缓存区中的所有数据刷新到内存中。

    4.JAVA对象头

           如果对象是数组,则对象头尾3个字宽,否则为2个字宽;JAVA对象头里的MarkWord里默认存储对象的HashCode、分代年龄和锁标记位。

    MarkWord

    5.锁的升级和对比

    锁一共有4种状态,由低到高分别为:无锁、偏向锁、轻量级锁、重量级锁,锁状态会随着竞争情况逐渐升级,锁可以升级但不能降级;

    偏向锁

           当一个线程访问同步代码块并获取锁时,会在对象头和栈帧的锁记录里面存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和释放锁(如果锁仍然偏向该线程,若不则CAS操作将对象头的偏向锁指向该线程),只需简单测试对象头的偏向锁记录是否仍然指向该线程。
           偏向锁的撤销,出现竞争才释放锁,撤销需要等待全局安全点,暂停并检查用于偏向锁的线程,若不处于活动状态则置为无锁状态,若线程仍活着则暂停并恢复对象头为无锁或标记对象不适合作为偏向锁,然后唤醒线程;

    轻量级锁

    • 加锁
      在当前线程的栈帧中创建存储锁记录的空间,并复制对象头中的Mark Word到锁记录中(Displaced Mark Word),然后使用CAS将对象头中的Mark Word替换为指向锁记录的指针,成功则获得锁,失败则表示有其他线程竞争锁,尝试自旋来获取锁;

    • 解锁,通过CAS操作将Displaced Mark Word替换回对象头,成功(a &b)则表示没有竞争;失败则锁膨胀成了重量级锁,唤醒等待的线程,但都会释放锁;
      a. 对象头中的Mark Word中的锁记录是否仍然指向当前线程锁记录;
      b. 拷贝在当前线程锁记录的Mark Word信息是否与对象头中的Mark Word一致;

      锁原理图

    6.原子操作的实现

    1) 处理器如何实现原子操作?

    • 总线锁(开销大),使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞,其与内存的通信被锁住,那么该处理器可以独享共享内存
    • 缓存锁,保证同一时刻对某个内存地址的操作是原子的,内存区域如果被缓存在处理器的缓存行中,并且在LOCK操作期间被锁定,那么当将缓存写回内存时,通过修改内部的内存地址并允许它的缓存一致性来保证操作的原子性。但是当操作的数据不能被缓存或操作的数据跨越多个缓存行时处理器会调用总线锁定,还有部分处理器不支持缓存锁定;

    2) JAVA如何实现原子操作?

    • 循环CAS操作,利用处理器提供的CMPXCHG指令实现;

    CAS实现原子操作的三大问题:
       1.ABA问题,一个值从A→B→A变化,使用CAS检查会发现值无变化,解决办法是变量前面追加版本号;
       2.循环时间长开销大,自旋CAS如果长时间不成功会给CPU带来非常大的执行开销;
       3.只能保证一个共享变量的原子操作,将多变量合并,JDK1.5提供的AtomicReference 类来保证引用对象的原子性,可以将多个变量封装到一个对象中来进行CAS;

    • 使用锁机制来实现原子操作,保证了只有获得锁的线程才能够操作锁定的内存区域,除了偏向锁,JVM实现锁的方式都采用了循环CAS,在进入时通过循环CAS获取锁,在退出同步块时使用循环CAS来释放锁;

    7.从源代码到指令序列的重排序

           1)编译器优化重排序,在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序;
           2)指令级并行重排序,现代处理器采用指令级并行技术来将多条指令重叠执行,如果不存在数据依赖,处理器可以改变语句对应机器指令的执行顺序;
           3)内存系统重排序,由于处理器使用缓存和读/写缓冲区,使得加载和存储操作看上去是乱序执行的;
           上述1)属于编译器重排序,2)3)属于处理器重排序,可能会导致内存可见性问题。对于1)JMM的编译器重排序规则会禁止特定类型的重排序,
           对于2)和3),JMM的处理器重排序规则会要求JAVA编译器在生成指令序列时,插入特定类型的内存屏障指令,通过内存屏障指令来禁止特定类型的重排序;
           数据依赖性:如果两个操作访问同一个变量,且这两个操作有一个为写操作,此时这2个操作之间存在数据依赖性,在重排序时,不会改变存在数据依赖关系的2个操作的执行顺序;

    8.顺序一致性模型&JAVA内存模型

    顺序一致特性:一个线程中的所有操作必须按照程序的顺序来执行;(不管程序是否同步)所有线程都只能看到一个单一的操作执行顺序。
    未同步程序的执行特性:JMM只提供最小安全性,线程执行时读取的值要么是之前某个线程写入的值,要么是默认值(0,NULL,FALSE)。为了实现最小安全性,JVM在堆上分配对象时,首先会对内存空间清零,然后才会在上面分配对象,在已清理的内存空间分配对象时,域的默认初始化已经完成。
    未同步程序在两个模型中的差异
    1)顺序一致性模型保证单线程内的操作会按程序顺序执行,而JMM不保证(临界区代码重排序);
    2)顺序一致性模型保证所有线程只能看到一致的操作执行顺序,而JMM不保证(临界区代码重排序);
    3)JMM不保证对64位的Long和Double变量的写操作是原子的,而顺序一致性模型保证对所有的内存读写操作都具有原子性;

    9.final域

    final域的重排序规则
     a)在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序(保证在对象引用为任意线程可见之前,对象的final域已经被正确的初始化,保证在构造函数内部,不能让这个被构造对象的引用为其他线程所见),在final写之后,构造函数返回之前插入StoreStore内存屏障;
     b)初次读一个包含final域的对象的引用,与随后初次读该对象的final域,这两个操作之间不能重排序(保证在读一个对象的引用之前,一定会先读包含这个final域的对象的引用),在final读之前插入LoadLoad内存屏障;

           X86处理器不会对写-写和间接依赖关系的操作进行重排序,所以在X86处理器中final域的读/写不会插入任何内存屏障。
           JSR-133中,只要对象正确构造(对象引用不会由构造器溢出),那么不需要同步就可以保证任意线程都能看到final域在构造函数中被初始化之后的值。

    10.双重检查锁定与延迟初始化

    延迟初始化:推迟高开销的对象初始化操作,在使用对象时才进行初始化。
    双重检查锁定:直接对getInstance()加锁存在具体的开销。

    if(instance == null){//1  
      synchronized(Main.class){//2  
        if(instance == null){//3  
          instance = new Main()//4    
        }  
      }  
    }  
    

    存在的问题
           第4行,代码读到instance不为null时,对象可能还没有完成初始化;创建一个对象可以分为三步:a.分配对象内存空间,b.初始化对象,c.将对象引用指向刚分配的内存地址,由于在单线程中b,c重排序不会改变执行结果,但在多线程并发情况下,b,c的重排序可能导致线程在创建对象时先执行c而导致其他线程判断instance不为空,但此时对象初始化并未完成。

    解决方案:
    1) 将引用对象instance声明为volatile,实际为禁止b,c的重排序;
    2) 基于类初始化

    在发生下列任意一种情况时,一个类/接口类型T将被立即初始化:

    • T是一个类,而且一个T类型的实例被创建(new)

    • T是一个类,且T中声明的静态方法被调用

    • T中声明的一个静态字段被赋值

    • T中声明的一个静态字段被调用,且该字段不是一个常量字段

    • T是一个顶级类,且一个断言语句嵌套在T内部被执行

    JVM在类初始化期间会获取与该类对应的初始化锁,并且每个线程至少获取一次锁来确保该类已被初始化。
    类初始化过程中的同步处理:
      第一阶段:通过在Class对象上同步(获取Class对象初始化锁),来控制类或接口的初始化。获取锁线程会一直阻塞直到获取到该初始化锁。

    第二阶段:线程A执行类的初始化,同时线程B在初始化锁对应的condition上等待;

    第三阶段:线程A设置state=initialized,唤醒在condition中等待的所有线程;

    第四阶段:线程B结束类的初始化处理;

    第五阶段:线程C执行类的初始化的处理,过程同上表(不会在condition中等待,只经历一次锁的获取和释放);

    11.线程

    线程是操作系统调度的最小单位;
    1.为什么要使用多线程?

    • 更多的处理器核心,由于一个线程在同一时刻只能运行在一个处理器核心上,随着处理器上的核心数量越来越多,使用多线程能够同时使用多个处理器核心,显著减少程序处理时间。
    • 更快的响应时间,使用多线程技术将数据一致性不强的操作派发给其他线程处理,最大化利用CPU时间片。
    • 更好的编程模型

    2.线程的状态

    3.Daemon线程(守护线程)
           当一个JAVA虚拟机中不存在非Daemon线程的时候,虚拟机将会退出;通过Thread.setDaemon(true)进行设置;Daemon属性需要在启动线程之前设置,不能在启动线程之后设置。当所有非Daemon线程结束,虚拟机中的所有Daemon线程需要立即终止,并不执行finally块。

    4.构造线程(Thread.init(…)方法)
           线程对象在构造时需要提供线程所需要的属性,如线程所属的线程组、线程优先级、是否时Daemon线程等;一个新构造的线程对象是由其parent线程(创建新线程的线程)来进行空间分配的,child线程继承了parent是否为Daemon、优先级和加载资源的contextClassLoader以及可继承的ThreadLocal,同时还会分配一个唯一ID来标识此child线程。

    5.线程中断

    • isInterrupted()判断是否被中断,不会影响中断标志;如果线程处于终止状态,即使线程已经被中断过,依旧返回false;
    • interrupted()判断是否被中断,并复位中断标志;
    • interrupt()对线程进行中断;

           在抛出InterruptedException之前,虚拟机会先将该线程的中断标志复位;
           安全地终止线程:通过在run()的循环中加入对线程中断标志的判断或者单独设置一个标志,通过对该标志的置位来退出循环,进而退出此线程;

    6.过期的suspend()、resume()和stop()。suspend()暂停此线程,不释放占有的资源(比如锁);resume()恢复此线程;stop()停止此线程,不保证线程资源被正常释放;

    7.等待/通知机制(wait/notify)

    • notify()通知一个在对象上等待的线程,使其从wait()方法返回,返回前提是该线程获得了对象的锁
    • notifyAll()通知所有等待在该对象上的线程
    • wait()调用该方法的线程进入WAITING状态,释放对象锁,只有等待其他线程的通知或被中断才返回
    • wait(long)超时等待一段时间,单位是毫秒,如果时间到没有通知则返回
    • wait(long,int)对于超时时间的细粒度控制,可以达到纳秒

    注意:
      a 使用wait()、notify()、notifyAll()时需要先对调用对象加锁;
      b 调用wait()方法后,线程状态由RUNNING变为WATING,并将线程放入对象的等待队列;
      c A线程调用notify()和notifyAll()后,等待线程B不会立即从wait()返回,需要A线程释放锁,等待线程B才有机会从wait()返回;
      d notify()方法将等待队列的一个等待线程从等待队列中移到同步队列,而notifyAll()则时将等待队列中的所有线程全部移到同步队列,被移动线程由WATING状态变为BLOCKED状态;
      e 从wait()方法返回的前提时获得了调用对象的锁;

    12.LOCK与synchronized

    LOCK接口提供的synchronized关键字不具备的主要特性
     1.尝试非阻塞地获取锁,当前线程获取锁时,如果此刻锁没有被其他线程获取,则成功获取该锁;
     2.能中断地获取锁,与synchronized不同,获取锁的线程能够响应中断,当获取到锁的线程被中断时,会抛出中断异常,同时释放锁;
     3.超时获取锁,在指定的截至时间之前获取锁,如果截至时间到仍无法获取锁,则返回;

    13.队列同步器(AbstractQueuedSynchronizer)

    用于构建锁或其他同步组件的基础框架,使用一个int变量表示成员状态,通过内置的FIFO队列来完成资源获取线程的排队工作。

    1.同步队列
           当前线程获取同步状态失败时,同步器会将当前线程及其等待状态等信息构成一个NODE并将其加入同步队列(双向链表),同时会阻塞当前线程,当同步状态释放时,会把首节点的线程唤醒,使其再次尝试获取同步状态。

    2.独占式同步状态获取与释放
           调用同步器的acquire(int arg)方法获取同步状态:通过调用自定义的tryacquire()来线程安全地获取同步状态,如果失败,则构造同步节点并通过addWaiter(Node node)方法将该节点加入到同步队列的尾部,最后调用acquireQueue(),使得该节点以“死循环“的方式获取同步状态,如果获取不到则阻塞节点中的线程,被阻塞线程的唤醒主要依靠前驱节点的出队或阻塞线程被中断来实现,只有前驱节点是头节点才能尝试获取同步状态,这是为什么?
     a.头节点是成功获取到同步状态的节点,而头节点的线程释放了同步状态之后,将会唤醒其后继节点,后继节点的线程被唤醒后需要检查自己的前驱是否是头节点;
     b.维护同步队列的FIFO原则;

           在释放同步状态时,同步器调用tryRelease()方法释放同步状态,并唤醒头节点的后继节点;

    3.共享式同步状态获取与释放
           通过调用tryAcquireShared()方法尝试获得同步状态,返回值大于等于0,表示能够获取到同步状态,若失败则进入自旋过程doAcquireShared(),当前驱是否为头节点时尝试获取同步状态,如果返回值大于等于0,则成功并从自旋过程中退出。
           释放过程与独占式的区别主要在于tryReleaseShared()方法必须通过循环和CAS确保同步状态线程安全释放
    4.独占式超时获取同步状态
           通过自旋,当前驱节点为头节点时尝试获取同步状态,失败则判断是否超时,没有则重新设置超时时间,然后让当前线程等待,超时则直接返回;
           当nanosTimeout小于等于spinForTimeoutThreshold(1000纳秒),则不会使该线程进行超时等待,而是进入快速自旋过程;

    14.重入锁

           表示该锁能够支持一个线程对资源的重复加锁,该锁还支持获取锁时的公平和非公平性选择,如果在绝对时间上,先对锁进行获取的请求一定先被满足,则这个锁是公平的,反之,是不公平的。
           ReentrantLock通过判断当前线程是否为获取锁的线程来决定操作是否成功,成功获取锁的线程再次获取锁只是增加了同步状态值,在释放同步状态时减少相应的值。

    15.公平锁与非公平锁

           公平性与否时针对获取锁而言,如果一个锁是公平的,那么锁的获取顺序应该符合请求的绝对顺序,也就是FIFO。
           非公平锁,只要CAS设置同步状态成功,则表示当前线程获取了锁,刚释放锁的线程重新获得同步状态的几率非常大。虽然可能造成线程“饥饿”,但极少的线程切换,保证了更大的吞吐量
           公平锁,在非公平锁的基础上,加入了对前驱节点的判断,只有等前驱节点获取并释放了锁之后才能获取锁。保证了锁的获取按照FIFO原则,代价是大量的线程切换。

    16.读写锁(ReentrantReadWriteLock)

           读写锁在同一时刻允许多个读线程访问,但在写线程访问时,所有其他线程都被阻塞;ReentrantReadWriteLock通过一个整形变量的高16位维护读状态,低16位维护写状态。
    1.写锁的获取与释放
           获取写锁时,在重入条件之上增加了对读锁是否存在的判断;释放写锁时,每次释放减少写状态(低16位),当写状态为0表示写锁已经被成功释放,并置获取锁线程为null。
    2.读锁的获取与释放
           获取读锁时,如果其他线程已经获取写锁,则当前线程获取读锁失败,进入等待状态;如果当前线程获取了写锁或者写锁未被获取,则当前线程成功获取读锁;先快速获取,如果失败则“死循环”获取读锁。
           释放读锁时,读状态减少1<<16;
    3.锁降级
           锁降级指写锁降级成为读锁,拥有写锁,再获取读锁,随后释放当前拥有的写锁。中间获取读锁是为了保证数据的可见性,如果当前线程A不获取读锁直接释放写锁,假设此刻有另一个线程B获取了写锁并修改了数据,那么当前线程A无法感知线程B对数据的更新。同理不支持锁升级。
    17.Condition
           每个Condition对象都包含着一个队列(等待队列),用于实现等待/通知功能。
    1.等待队列
           等待队列是一个FIFO队列,队列中每个节点包含一个线程引用,该线程就是在Condition上等待的线程,如果一个线程调用了Condition.await()方法,该线程会释放锁、构造成节点并加入等待队列进入等待状态。
           在Object的监视器模型上,一个对象包含一个同步队列和等待队列;而并发包中的Lock拥有一个同步队列和多个等待队列。
    2.等待
           调用Condition的await()方法,使当前线程进入等待队列并释放锁,同时线程状态变为等待状态。相当于同步队列的首节点移动到了等待队列的尾节点。
    3.通知
           调用Condition的signal()方法,若当前线程获取了锁,将会唤醒在等待队列中等待时间最长的节点(首节点),在唤醒节点前会将节点移到同步队列(队尾),最后通过LockSupport唤醒节点中的线程

    18.ConcurrentHashMap

           不支持key或value为null;hash值通过key的hash值与其无符号右移16位的值异或并除去符号位得到。
    1.为什么要使用ConcurrentHashMap
           在多线程环境下,使用HashMap进行put操作可能会引起死循环,因为多线程会导致HashMap的Entry链表形成环形数据结构;
           Hashtable使用synchronized来保证线程安全,效率低下。
    2.ConcurrentHashMap如何实现线程安全性
           在JDK1.7中,ConcurrentHashMap容器通过多把锁,每一把锁用于锁容器的其中一部分数据,即分段加锁,当多线程访问容器的不同数据段的数据时,线程间就不会存在锁的竞争,从而有效提高了并发访问效率。
           在JDK1.8中,若桶为空则直接通过CAS操作添加节点,否则synchronized锁住链表的头节点或者树的根节点后进行添加。

    19.并发工具类

    1.CountDownLatch
           运行一个或多个线程等待其他线程完成操作接受一个int类型参数N(N>=0)作为计数器,每次调用countDown方法时N就会减1,调用await方法会阻塞当前线程,直到N变为0。
           线程A调用countDown方法happen-before线程B调用await方法
    2.同步屏障CyclicBarrier
           可循环使用的屏障,让一组线程到达一个屏障(同步点)时被阻塞,直到最后一个线程到达屏障,可以用于多线程计算数据。
           默认构造方法是CyclicBarrier(int parties),参数表明屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经达到屏障,然后当前线程被阻塞
    3.CyclicBarrier和CountDownLatch的区别

    • CountDownLatch的计数器只能使用一次,CyclicBarrier可以使用reset()方法重置;
    • CyclicBarrier提供方法来判断阻塞线程是否被中断(isBroken),获取阻塞线程数量(getNumberWaiting)

    4.信号量Semaphore
           用来控制同时访问特定资源的线程数量,通过协调各个线程,保证合理的使用公共资源。可以用做流量控制,比如数据库连接。
    5.Exchanger
           交换者Exchanger用于线程间数据交换,提供一个同步点交换彼此数据。可以用于遗传算法、校对工作。
           A线程通过执行exchange()方法,并等待B线程也执行exchange,两个线程都到达同步点,可以交换数据。可以设置超时等待。

    20.线程池

    好处:

    • a.降低资源开销:重复利用已创建的线程降低创建和销毁造成的开销;
    • b.提高响应速度:任务到达时,不需要等待线程创建就能立即执行;
    • c.提高线程的可管理性:可以进行统一分配、调度和监控;

    线程池的处理流程:

    • 线程池判断核心线程池里的线程是否都在执行任务,如果是则进入下一步;
    • 线程池判断工作队列是否已经满,没满则将任务存入工作队列,否则进入一下步;
    • 线程池判断线程池的线程是否都处于工作状态,没有则创建一个工作线程来执行任务,否则执行饱和策略;

    ThreadPoolExecutor执行executor方法分下面四种情况:

    • 当前运行线程数少于corePoolSize,则创建新线程来执行任务(需全局锁);
    • 当前运行线程数大于等于corePoolSize,则将任务加入BlockingQueue;
    • 如果任务无法加入BlockingQueue(队列已满),则创建新的线程来处理任务(需全局锁);
    • 如果创建新线程使当前运行线程超出maximumPoolSize,则任务被拒绝;

    线程池的使用:
    1)线程池的创建

    new ThreadPoolExecutor (corePoolSize, maximumPoolSize, keepAliveTime, milliseconds, runnableTaskQueue, handler)  
    

    主要参数

    • CorePoolSize(线程池的基本大小):提交任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,直到需要执行的任务数大于线程池基本大小就不再创建,可以通过调用prestartAllCoreThreads方法提前创建并启动所有基本线程;

    • RunnableTaskQueue(任务队列):用于保存等待执行任务的阻塞队列,可以选择ArrayBlockingQueueLinkedBlockedQueue,SynchronousQueue, PriorityBlockingQueue;

    • MaximumPoolSize(线程池最大数量):线程池运行创建的最大数量。若队列满且已创建线程数小于最大线程数,线程池会再创建新的线程执行任务;

    • ThreadFactory:用于设置线程工厂,通过线程工厂给每个创建出来的线程设置有意义的名字;

    • RejectedExecutionHandler(饱和策略):当线程池和任务队列都满了,线程池处于饱和状态,必须采取一种策略处理提交的新任务。
          JDK1.5提供了四种策略:
            - AbortPolicy:直接抛出异常;
            - CallerRunsPolicy:只用调用者所在线程来运行任务;
            - DisCardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务;
            - DiscardPolicy:直接丢弃,不做处理;

    • f.KeppAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间,如果任务很多,任务执行时间短,可以调大来提供线程存活;

    • g.TimeUnit(线程活动保持时间的单位):DAYS, HOURS, MINUTES等

    相关文章

      网友评论

          本文标题:《JAVA并发编程的艺术》笔记(review中)

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