《java并发编程的艺术》学习小结

guangdeshishe guangdeshishe     2022-11-28     651

关键词:

java并发编程的艺术

第一章 并发编程的挑战

  • 上下文切换:cpu通过时间片让不同线程轮流运行,从线程状态保存到下一次线程运行这个过程就是一次上下文切换
  • 多线程并不一定比单线程快,因为多线程会有线程创建和上下文切换的开销
  • 如何减少上下文切换:
    • 无锁并发:锁竞争会导致其他线程挂起,加锁、解锁会导致线程频繁上下文切换
      • 不同线程处理不同分段数据
      • 使用CAS算法:比如Atomic包
    • 减少线程数
    • 协程:在单线程中实现多个任务同时运行
  • 避免死锁办法:
    • 避免一个线程同时获取多个锁资源
    • 尝试使用定时锁,lock.tryLock(timeout)
  • 资源限制导致并发效率低,例如:服务器带宽只有2M/s,某个资源下载限速是1M/s,通过并发10个线程,也不能达到10M/s的下载速度
    • 网络带宽、硬盘读写速度、cpu处理速度等资源限制

第二章 Java并发机制的底层实现原理

  • Volatile:volatile修饰的变量在多线程下能保证它的可见性,也就是其他线程在访问该变量值时总是能获取到最新的值;通过内存屏障还可以防止指令重排
    • 实现原理:可见性是在写操作时增加lock指令,它会使得cpu高速缓存区内的值修改后立即同步到内存中,同时会让其他高速缓存区内的数据失效,当其他线程访问该变量值时会从新从内存中读取到高速缓存区中
    • 防止指令重排:cpu执行的指令时不一定是按照固定的顺序来执行的,会根据情况来调整执行顺序,比如在单例模式中对象创建时,一般要经过这三个步骤,分配内存获取对象内存地址、调用构造方法初始化,最后把地址赋值给变量;由于指令重排,有可能出现先给变量赋值,后调用构造方法初始化的情况,就是会导致对象虽然不为null。但是由于对象还没有初始化完成从而导致异常,volatile就可以避免这种情况发生
    • 使用Volatile变量时队列优化:可以通过填充的字节数到64个字节,这样在缓存行是64个字节宽的CPU上执行,入队和出队的效率会更高,这是因为如果对头和队尾都不足64位字节情况下,会把它们读取到同一个缓存行中,当操作队头时,根据缓存一致性原则会锁定缓存行,这个时候队尾就不能操作了,会影响性能
      • JUC包中jdk7新增的LinkedTransferQueue就是这种实现方式,仅适用于缓存行是64位宽的处理器,以及写操作频繁时使用,如果写不频繁,由于增加了字节数会导致一定的性能消耗
  • Synchronized:JVM通过Monitor对象在编译时同步代码前后加入monitorenter和monitorexit指令来实现的
    • 不管是对象锁还是类锁,多线程访问不同synchronized方法也是需要等待的
    • jdk1.6开始对Synchronized进行个各种优化,加入了偏向锁和轻量级锁,锁就有个四种状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态
    • 对象内存结构:对象头信息(MarkWord+对象所属类的指针+数组长度)+实例数据+对齐填充
      • MarkWord:HashCode+分代年龄+是否偏向锁+锁状态
    • 锁膨胀过程:
      • 当一个线程进入同步方法或者同步代码块时,会先检查锁状态,如果值是01,表示无锁或者偏向锁状态;如果已经是偏向锁状态,并且线程id是自己的,则直接运行代码;否则执行下一步
      • 通过cas操作尝试将偏向锁状态改为1,同时将MarkWord中的线程ID改成自己的;如果成功则获取锁成功,如果失败则表示有其他线程在竞争该锁,就会执行释放偏向锁
      • 当达到安全点的时候,原来获取偏向锁的线程会暂停,检查它的状态,如果线程已经处于不活动状态或者退出了同步代码块,则会重新设置为无锁状态(线程id置空+是否偏向锁为0+锁标记位为01);如果线程还在执行同步代码块中的代码,则升级为轻量级锁;
      • 在升级轻量级锁之前会将已经获取偏向锁的线程在栈帧中开辟一块锁记录空间,将MarkWord信息拷贝到锁记录中,修改锁状态为00,并将对象中MarkWord指向该锁记录地址,也就是把升级后的轻量级锁给原有获取了偏向锁的线程
      • 接着当前线程也会开辟一块空间记录锁记录,将MarkWord拷贝一份到锁记录中,并通过CAS操作尝试将对象头中MarkWord中地址指向当前线程的锁地址,如果成功则表示获取到了轻量级锁,如果多次失败后;会升级为重量级锁,对象的MarkWord就会记录Monitor对象的地址并修改锁状态为10;其他线程会阻塞并等待唤醒
      • 原来持有轻量级锁的线程释放锁时会将线程栈帧中的锁记录通过cas操作替换对象头中的MarkWord数据,如果成功则表示没有发生竞争,如果失败则表示存在竞争,此时锁已经升级成重量级锁了,然后会唤醒其他线程重新竞争锁
    • 锁的种类:
      • 偏向锁:锁状态[01];对象头Markword中记录获得偏向锁的线程ID;解锁时清空MarkWord中记录的线程id,并重置锁状态为[01];适合没有竞争,只有一个线程访问同步代码块的情况;
      • 轻量级锁:锁状态[00];拷贝对象头中的Markword信息到栈帧锁记录中,cas操作将对象头中Markword替换为锁记录的地址;解锁时cas操作将Markword信息从锁记录拷贝回来;适合竞争的线程较少,并且获取锁等待时间较短的场景;
      • 重量级锁:锁状态[10];将对象头中Markword信息修改为重量级锁的地址,也就是Monitor对象地址;只有线程获取到monitor锁时才能访问同步代码块,其他线程会阻塞进入休眠,等待解锁后被唤醒重新竞争锁资源;适合线程竞争较多,获取锁等待时间较长的场景
      • GC:锁状态[11]
    • 为什么锁的膨胀只能升级不能降级?
      • 因为锁一旦升级了,说明有更多的线程竞争资源,cas操作是通过自旋来实现的,一旦其他线程不能很快获取到锁资源就会一直自旋等待,浪费了cpu资源,而如果升级到了重量级锁,未获得锁的线程会阻塞进入休眠状态,就不会浪费cpu资源了
    • 偏向锁记录的线程ID会导致HashCode值丢失吗?
      • 不会,因为计算HashCode的时候,如果已经是偏向锁状态,会膨胀为轻量级锁或者重量级锁,将MarkWord信息保存到栈帧锁记录中或者Monitor对象中,其中就有HashCode值
      • 如果MarkWord已经记录有HashCode的情况下不会进入偏向锁
  • 原子操作实现原理
    • 处理器实现原子操作:
      • 总线锁:当一个cpu处理内存数据时,会在总线上发送Lock信号,其他cpu会被阻塞,避免同时访问内存
      • 缓存锁:当cpu修改缓存行中数据时会锁定缓存行中数据,回写到内存后,缓存一致性机制会使得其他cpu的缓存行中数据失效,需要重新从内存中读取最新数据到缓存行中
        • 当操作的数据无法缓存到CPU三级缓存中或者操作的数据跨越多个缓存行数据时会调用总线锁定
    • java实现原子操作:
      • 锁机制:ReentrantLock
      • 自旋CAS操作:循环执行cas操作直到成功为止;AtomicInteger\\AtomicBoolean(volatile+cas实现)等
        • ABA问题:cas操作是通过比较是否与预期的值一致,如果相同就执行替换操作,但是如果一个变量的值一开始是A,然后变成B,最后有变回A,这个时候的A其实是发生变化了的,如果对修改操作敏感的场景下就会出现问题;解决办法就是增加一个字段记录值的版本,每次修改值则版本号+1;cas操作时同时检查值和版本号;JDK提供了AtomicStampedReference类解决ABA问题
        • 自旋时间太长会占用CPU资源:可以根据自旋次数或者超时来取消操作

第三章 Java内存模型

  • 在每个线程中会有主内存的一份拷贝,叫做本地内存(或者工作内存),线程中操作的变量都是本地内存中的,当修改完之后会同步到主内存
  • 主内存和工作内存之间的8大操作:
    • Lock(锁定):作用于主内存变量,锁定变量为一个线程独占
    • UnLock(解锁):作用于主内存变量,对变量解锁
    • read(读取):作用于主内存变量,将变量值从主内存传输到工作内存
    • load(载入):作用于工作内存变量,将read获取到的值赋值给工作内存中的变量
    • use(使用):作用于工作内存,将变量值传给执行引擎执行
    • assign(赋值):作用于工作内存,将执行引擎返回的值赋值给变量
    • store(存储):作用于工作内存,将工作内存中的值传递到主内存
    • write(写入):作用于主内存,将上一步store操作获取到的值赋值给主内存中的变量
  • happens-before、as-if-serial:避免指令重排序导致结果错误
  • 指令重排序:为了优化处理器的并行处理速度
  • 顺序一致性:一个线程所有操作必须按照程序的顺序来执行;每个操作都必须是原子性且对其他线程可见的
  • volatile:可以保证可见性和防止指令重排序,但是不能保证操作的原子性;保证可见性是当volatile变量被修改后会立刻同步都主内存,同时让其他工作空间中的值失效,其他工作空间总是能读取到内存中最新的值;通过在字节码中加入内存屏障防止指令重排实现
  • 锁在释放时会把数据同步到主内存,保证可见性
  • final:可以保证其他线程读取到时都是初始化之后的值,但是需要保证final变量所在的对象在构造方法初始化阶段不能逃逸出来;
  • 双重检查锁定:可能会导致获取到的对象还没有初始化,因为实例初始化分为三个步骤:1、分配内存空间;2、对象初始化;3、变量指向内存地址;由于第二步和第三步可能发生指令重排,也就是变量不为null时,对象可能还没初始化;
    • 解决办法:1、变量加上volatile,禁止指令重排序;2、使用静态内部类初始化实例;

第四章 java并发编程基础

  • 线程状态:
    • NEW:线程被创建,但是还没调用start方法
    • RUNNING:运行状态,线程准备就绪和已经在运行统称为运行中
    • BLOCKED:阻塞状态,属于被动等待,比如等待锁的释放
    • WAITING:等待状态,主动调用Object.wait()、Thread.join()、LockSupport.park(thread)等方法,需要其他线程调用Object.notify()/notifyAll等才能唤醒
      • wait必须要在synchronized内调用,调用后线程会进入等待状态,并且释放锁;sleep不会释放锁;nofity只能随机唤醒某个线程;park和unpark可以指定对某个线程休眠或者唤醒
      • 调用notify后,线程会进入BLOCKED阻塞状态,重新获取锁后才会从等待的地方恢复
      • Thread.join实现原理是调用等待线程的Objet.wait方法,当线程结束后会调用Object.notifyAll唤醒线程
    • TIME_WAITING:超时等待状态,Thread.sleep(),等待一定时间后会自动恢复
    • TERMINATED:终止状态,表示当前线程已经执行完毕结束了
    • 终端命令:
    • jps:查看所有java进程id
    • jstack pid:查看某个进程所有线程状态
  • Daemon线程:当jvm中不存在非Daemon线程时,虚拟机会退出,可以理解为不重要的线程,其中的finally代码不一定会执行
  • 线程的终止:
    • interrupt:只是一个标记位表示中止,不一定会终止线程
    • suspend/resume/stop:已经过时了,因为suspend虽然可以暂停线程,但是暂停期间还持有锁,容易造成死锁问题,而stop虽然可以终止线程,但是会使得线程没有机会释放资源
    • 标志位:在run方法中添加标记为判断
  • javap xxx.class:查看字节码
  • Synchronized:通过monitorenter/monitorexit指令实现;等待的线程会进入BLOCKED阻塞状态
  • PipedOutputStream/PipedInputStream/PipedReader/PipedWriter:管道输入输出流,用于线程之间传输数据,传输媒介是内存
  • ThreadLocal:该变量在每个线程都有一份,并且可以线程内独立变化不会影响到其他线程里的值,实现原理是在Thread类里面有个ThreadLocalMap用来保存当前线程所有的ThreadLocal变量值,这个Map把ThreadLocal变量作为key,把ThreadLocal变量的值作为Value;ThreadLocalMap内部结构其实是一个Entry数组,Entry继承自WeakReference弱引用,保存着ThreadLocal这个变量和对应的值,数组通过ThreadLocal的hashcode&数组长度-1作为数组的索引
  • CountDownLatch:可以保证多个线程同时开始或者都结束后才继续;实现原理主要是通过它父类AQS实现,当调用await时会判断状态值是否为0,如果不是则会调用LockSupport.park方法阻塞当前线程,当其他线程调用countdown方法时,status状态值会减1,直到status==0时再调用unpark唤醒线程
  • Collections.synchronizedList:获取线程安全的List;内部实现是将List封装到另一个List中,当调用List的一些方法时,会加上Synchronized同步锁

第五章 Java中的锁

  • AQS(AbstractQueuedSynchronizer):队列同步器
  • JUC包实现锁相关类的基础
  • 通过CAS操作的状态stat
  • 同步等待队列(FIFO);获取同步状态失败时会被构建成Node节点加入到队列中,然后每个节点都在不停的自旋,并判断如果前置节点是队列中的头节点则尝试获取同步状态(为了保证FIFO特点),如果成功则将当前节点设置为头结点,接着调用LockSupport.park阻塞当前线程等待被唤醒;队列由双向链表实现
  • 当同步状态被释放后,队首线程会被唤醒重新获取同步状态;队首线程释放同步状态后又会唤醒它下一个节点线程
  • 共享式获取同步状态与独占式主要区别在于,共享式可以允许多个线程同时获取锁,比如对文件的读操作,或者服务端处理请求时可以限制同时有几个线程可以访问Semaphore,而独占式只允许同一时间允许一个线程访问
  • 超时等待:如果设置的超时时间小于1000纳秒,也就是1微妙,则会忽略超时,因为时间太短超时也不准确
  • ReentrantLock:支持公平和非公平锁,公平的意思是先进队列的先获得锁,非公平是指所有线程进行竞争;默认是非公平锁
    • 可重入锁特点:同一个线程如果已经获取到了锁,则可以多次调用Lock再次获取锁,每次获取锁state状态会+1;但是释放锁时也需要释放N次,其他线程才能重新获取到锁
    • 公平性:公平和非公平在获取锁时主要区别在于,公平锁会先判断当前节点是否有前置节点并且是队列头节点,如果有的话则优先获取锁;非公平锁性能更高,因为它会优先把锁分配给上一个获取到锁的线程减少线程切换开销;而公平锁会导致线程切换频繁
  • ReentrantReadWriteLock:同时支持可重入以及读写的锁,内部通过将int整形的状态划分成高16位表示读锁的状态,低16位表示写锁的状态
    • 写锁的获取需要保证当前没有线程获取到读锁,因为如果有读锁的情况下获取写锁会导致读到的数据不是最新的
    • 读锁的获取需要保证当前没有除了自己以外的其他线程获取到写锁,自身获取到写锁的情况下可以获取读锁
    • 锁的降级:线程先获取到写锁,然后获取到读锁,接着释放了写锁,这个时候就降级到了读锁,主要用于修改数据后又立刻需要使用数据的场景
    • 锁不支持升级:因为如果有多个线程同时获取到了读锁,其中一个升级为写锁修改了数据,那么其他线程读到的数据就不能保证是最新的
  • LockSupport:内部实现是调用了Unsafe的park和park方法;park方法中Blocker对象是用于标记当前阻塞等待的对象
  • Condition.await/signal/signalAll:依托于ReentrantLock.newCondition创建,内部维护一个等待队列
    • 当调用await方法时,当前线程必须获取到锁,然后Node节点会从同步队列中移除并创建新的Node节点放到Condition队列的末尾,然后调用LockSupport.park将当前线程休眠并释放锁;接着会唤醒同步队列中下一个节点去获取锁
    • 当调用signal方法时,也必须要先获取到锁,然后将Condition中等待定队列中的头结点转移到同步队列中并尝试获取锁,如果获取锁成功,则从调用await的地方返回
    • signal则相当于对Condition中每个节点都执行一遍signal方法;

第六章 Java并发容器和框架

  • HashTable是通过Synchronized保证线程安全,这会导致写方法被锁住后,也无法执行读取的方法,效率很低
  • HashMap在多线程环境下容易导致死循环,因为在put时可能会触发扩容操作,扩容是采用头插入,可能会导致链表出现死循环
  • CocurrentHashMap:采用了分段锁技术,把数据分成一段一段的,每一段数据由一把锁控制,当访问某一段数据时,不会影响其他段数据的读写
  • Segment分段锁继承自ReentrantLock
  • get方法不需要加锁,因为get方法涉及到的变量都使用了volatile修饰,保证了线程之间的可见性,每次都能获取到最新的值
  • 相比HashMap;它扩容时会先判断是否超过容量,超过时才扩容,而HashMap是插入后才扩容,可能会导致扩容后没有新元素插入从而浪费空间
  • 扩容时仅仅对当前Segment扩容,不会对整个ConcurrentHashMap扩容
  • ConcurrentLinkedQueue:使用cas保证线程安全
  • 阻塞队列:为空或者满时会阻塞队列
  • add/remove/element:抛出异常
  • offer/poll/peek:返回结果;或者超时返回结果
  • put/take:一直阻塞
  • ArrayBlockingQueue:数组结构组成的有界阻塞队列
  • LinkedBlockingQueue:链表结构组成的无界阻塞队列
  • PriorityBlockingQueue:支持优先级排序的无界阻塞队列
  • DelayQueue:延迟无界阻塞队列,只有延迟的时间到了才可以取出元素;可用于定时任务;或者缓存有效期检测
  • SynchronousQueue:不存储元素的阻塞队列,当队列加入一个元素时必须要等待消费了才能返回
  • LinkedTransferQueue:链表组成无界阻塞队列;相比普通LinkedBlockingQueue,它还支持transfer方法,用于实现类似SynchronousQueue效果,必须要等待消费了才能返回
  • LinkedBlockingDequeue:链表实现的双向无界阻塞队列
  • 阻塞实现原理:内部是通过两个Condition,一个用于控制队列满,一个用于控制队列空时的阻塞,通过调用condition的await和signal方法来阻塞和唤醒线程,而await和signal又是通过调用LockSupport的part/unpark方法将当前线程睡眠或者唤醒
  • Fork/Join框架:将复杂的任务分割成一个个很小的任务,然后将每个任务的结果合并起来得到最终的结果
  • 工作窃取算法:当多个线程同时执行很多任务时,有些线程会提前执行完,这时候它可以从其他线程的任务队列中窃取任务继续执行,提高执行效率,为了减少竞争,任务队列会采用双端队列,正常获取任务从队列头部获取,而窃取任务则从队列尾部获取任务
  • 缺点就是当双端队列中只有一个任务时,窃取任务时还是有可能会有竞争;由于采用多线程、多个双端队列,会造成更多系统资源的消耗
  • ForkJoinPool:包含一个ForkJoinTask数组和ForkJoinWorkerThread数组,前者保存要执行的任务,后者保存执行任务的线程
  • ForkJoinTask:fork方法主要用于将当前任务保存到Task数组中,然后交给线程去执行;join方法用于阻塞线程并获取结果
  • RecursiveTask:有返回结果的任务
  • RecursiveAction:没有返回结果的任务

第七章 Java中的13个原子操作类

  • AtomicBoolean/AtomicInteger/AtomicLong/:通过自旋调用Unsafe类里的cas操作实现,cas支持object/int/long操作,AtomicBoolean是通过将boolean转成int后使用cas操作实现的
  • AtomicIntegerArray/AtomicLongArray/AtomicReferenceArray
  • AtomicReference/AtomicReferenceFieldUpdater/AtomicMarkableReference
  • AtomicIntegerFieldUpdater/AtomicLongFieldUpdater/AtomicStampedReference:需要先调用newUpdater方法设置操作的类和字段;要更新的字段必须volatile修饰

第八章 Java中的并发工具类

  • CountDownLatch(countDown/await):等待多个线程都完成才能继续,内部实现是通过不断的循环判断计数器是否为0,为0 了才能继续,否在继续等待,countDown方法会将计数器减一并调用notify唤醒线程;不可以重复使用
  • CyclicBarrier(await):同步屏障,只有每个线程都达到await调用地方后才能继续;可以调用reset方法复用;和CountDownLatch相比,CountDownLatch调用coutnDown方法后线程可以继续执行,而CyclicBarrier线程调用await则进入的等待,不能继续执行
  • Semaphore(acquire/release):信号量,用于控制能有多少个线程同时访问某个资源;调用acquire方法时会判断允许访问的线程数是否已经满,满了则阻塞不允许访问
  • Exchanger:线程之间数据交换;当调用exchange方法时,会阻塞当前线程等到另一个线程也调用exchange方法,通过该方法可以拿到另一个线程的值,应用场景:当两个数据需要对比结果时可以用

第九章 Java中的线程池

  • 线程池关键参数:
  • corePoolSize:核心线程数
  • maximumPoolSize:最大线程数,包括核心线程数在内
  • keepAliveTime:线程执行完任务后等待多长时间关闭,核心线程需要设置allowCoreThreadTimeOut为true才有效
  • unit(TimeUnit):线程等待关闭时间的单位
  • workQueue(BlockingQueue):阻塞等待队列
    • ArrayBlockingQueue:有界阻塞队列
    • LinkedBlockingDequeue:链表实现无界等待队列
    • SynchronousQueue:不存储元素的阻塞队列,当队列加入一个元素时必须要等待消费了才能返回
    • PriorityBlockingQueue:支持优先级的无界等待队列
  • threadFactory(ThreadFactory):线程创建工厂类
  • handler(RejectedExecutionHandler):队列和线程池都满时的处理策略
  • AbortPolicy:直接抛出异常
  • CallerRunsPolicy:在调用者所在线程执行任务
  • DiscardOldestPolicy:丢弃队列中最早的任务,并加入当前任务到队列中
  • DiscardPolicy:丢弃当前任务
  • 线程池核心原理:
  • 当任务进入线程池时,首先判断核心线程数是否已满,如果未满,则创建新的核心线程池执行任务
  • 如果核心线程数已满,则判断等待队列是否已满,如果等待队列未满,则进入等待队列中;当线程执行完其他任务会从等待队列中取出新的任务执行
  • 如果等待队列满了,则判断总的线程数是否已满,如果未满,则开启新的普通线程执行任务
  • 如果总的线程数也已经满了,则执行对应的拒绝策略
  • 线程池的几种创建方法:
  • Executors.newFixedThreadPool:核心线程数固定,最大线程数等于核心线程数,等待队列是LinkedBlockingQueue无界等待队列
  • Executors.newSingleThreadPool:核心线程数为1的FixedThreadPool
  • Executors.newCachedThreadPool:核心线程数为0;最大线程数为整数的最大值,使用同步等待队列,SynchronousQueue,也就是来一个任务就创建一个线程
  • Executors.newScheduledThreadPool:核心线程数固定,最大线程数为整数的最大值,使用DelayQueue
    • DelayQueue封装了PriorityQueue,根据delay时间和优先级对任务排序,时间更早的和优先级更高的优先执行
    • 周期性执行实现是通过执行完一次后自动修改delay时间,然后重新放入队列中,相比Timer来说更灵活功能强大,Timer是单线程
  • FixedThreadPool和SingleThreadPool使用了无界阻塞队列,如果任务量过大的话可能会导致队列爆满内存溢出
  • CachedThreadPool和ScheduleThreadPool设置了线程最大数为整数的最大值,这可能会导致线程数过大从而导致内存溢出
  • 建议根据业务需求自定义ThreadPoolExecutor参数
  • 线程池任务提交:
  • submit:这种方式提交的会返回一个Feature对象用于获取执行结果,需要搭配Callable使用,如果使用runnable依然无法获取到结果
  • execute:无法获取执行结果
  • Callable支持返回结果和抛出异常,而runnable则不行
  • 线程池关闭:
  • shutdown:取消还没有开始执行的任务,已经在执行的任务等待执行完后关闭线程池
  • shutdownNow:取消所有未开始执行的任务,已经在执行的任务调用interrupt方法尝试中断线程
  • isTerminated:判断线程池是否完全关闭,isShutdown为true只能说明调用过shutdown/shutdownNow方法
  • 线程池的缺点和优化:
  • 核心线程默认不会超时后关闭,需要主动调用allowCoreThreadTimeOut
  • 任务提交后如果判断核心线程数量没满,则不管核心线程有没有空闲的,都会去创建新的核心线程;可以先判断有没有空闲的线程再考虑创建新线程
  • 当等待队列数很大的时候,可能会导致任务很久才能执行到,可以自定义等待队列,判断当最大线程数没满的时候,优先创建新的线程执行
  • 线程池类型:
  • CPU密集型:计算量比较大,占用cpu时间比较长的,应该尽量少配置线程数,一般cpu核心数+1个就可以了
  • IO密集型:由于IO操作不会一直在执行占用cpu,所以可以配置多点线程数,一般cpu核心数*2个
  • 获取cpu核心数:Runtime.getRuntime().availableProcessors()
  • 线程池监控,可以通过以下几个方法获取状态:
  • taskCount:当前需要执行的任务数
  • completedTaskCount:已完成的任务数
  • largestPoolSize:创建过的最大线程数
  • getPoolSize:当前线程池中线程的数量
  • getActiveCount:当前活动的线程数

第十章 Executor框架

  • FeatureTask内部是通过AQS实现,调用get方法时如果任务还没执行结束则会阻塞当前线程,当任务结束时会唤醒当前线程并返回结果

java并发编程艺术学习第二章java并发机制的底层实现原理学习记录volatile

章节介绍  这一章节主要学习java并发机制的底层实现原理。主要学习volatile、synchronized和原子操作的实现原理。Java中的大部分容器和框架都依赖于此。  Java代码==经过编译==》Java字节码==通过类加载器==》JVM(jvm执行字节码... 查看详情

并发编程学习小结

...量的需要,定制自己线程池的大小。虽然通过这个对并发编程已经有了一定的了解,但是个人认为自己目前对于java的并发编程只是局限于使用和浅层的了解阶段,在这里先对自己这段时间的学习做个总结。    首先... 查看详情

并发编程学习小结

...量的需要,定制自己线程池的大小。虽然通过这个对并发编程已经有了一定的了解,但是个人认为自己目前对于java的并发编程只是局限于使用和浅层的了解阶段,在这里先对自己这段时间的学习做个总结。    首先... 查看详情

深入理解java多线程-java并发编程的艺术

今天深度学习一下《Java并发编程的艺术》的第1章并发编程的挑战,深入理解Java多线程,看看多线程中的坑。注意,哈肯的程序员读书笔记并不是抄书,而是将书中精华内容和哈肯的开发经验相结合,给你系统地讲述我对相关知... 查看详情

java并发编程的艺术(5-10)学习总结(代码片段)

本文参考学习Java并发编程的艺术第5章Java中的锁5.1Lock接口synchronized没有的特性尝试非阻塞获取锁能够中断获取锁超时获取锁5.2队列同步器队列同步器AbstractQueuedSynchronizer用来构建锁,或者其它同步组件。用一个int成员变量表... 查看详情

java并发编程的艺术目录

第7章:JAVA中的13个原子操作类第8章:JAVA中的并发工具类第9章:JAVA中的线程池 查看详情

java并发编程的艺术记录

模拟死锁packagecom.gjjun.concurrent;/***模拟死锁,来源于《Java并发编程的艺术》*@Authorgjjun*@Create2018/8/12**/publicclassDeadLockDemo{privatestaticStringA="A";privatestaticStringB="B";publicstaticvoidmain(String[]args){D 查看详情

并发编程的挑战(java并发编程的艺术)

1.上下文切换CPU通过给每个线程分配CPU时间片来实现并发,切换过程中线程的信息从保存到再加载就是一个上下文切换。由于频繁的进行上下文切换,会消耗资源,所以并发不一定比串行快。可以通过Lmbench3测量上下文切换的时... 查看详情

《java并发编程的艺术》epub下载在线阅读,求百度网盘云资源

《Java并发编程的艺术》(方腾飞)电子书网盘下载免费在线阅读资源链接:链接:https://pan.baidu.com/s/19JrldXCS7yGVJadthE2VNw提取码:1dub  书名:Java并发编程的艺术作者:方腾飞豆瓣评分:7.4出版社:机械工业出版社出版年份... 查看详情

《java并发编程的艺术》读后笔记-part2(代码片段)

文章目录《Java并发编程的艺术》读后笔记-part2第二章Java并发机制的底层实现原理1.volatile的应用1.1volatile的定义与实现原理2.synchronized的实现原理和应用2.1Java对象头2.2锁的升级与对比3.原子操作的实现原理3.1处理器如何实现原子... 查看详情

《java并发编程的艺术》读后笔记-part1(代码片段)

文章目录《Java并发编程的艺术》读后笔记-part1第一章并发编程的挑战1.上下文切换1.1多线程就一定快吗?1.2如何减少上下文切换呢?2.死锁2.1避免死锁的几个方法3.资源限制的挑战《Java并发编程的艺术》读后笔记-part1第一... 查看详情

《java多线程编程核心技术》和《java并发编程的艺术》两本书的异同

...编程核心技术》:这本书让你入个门,整体上了介绍一些并发编程的基本API、常见场景和一些坑,推荐先看这本书,比较简单,适合新手,但是原理不够深入和《java并发编程的艺术》这本书从底层和实现原理开始讲起,深入java... 查看详情

java并发编程的艺术,解读并发编程的优缺点(代码片段)

并发编程的优缺点使用并发的原因多核的CPU的背景下,催生了并发编程的趋势,通过并发编程的形式可以将多核CPU的计算能力发挥到极致,性能得到提升。在特殊的业务场景下先天的就适合于并发编程。比如在图像处理领域,一... 查看详情

java并发编程的艺术,解读并发编程的优缺点(代码片段)

并发编程的优缺点使用并发的原因多核的CPU的背景下,催生了并发编程的趋势,通过并发编程的形式可以将多核CPU的计算能力发挥到极致,性能得到提升。在特殊的业务场景下先天的就适合于并发编程。比如在图像处理领域,一... 查看详情

并发编程学习小结

...量的需要,定制自己线程池的大小。虽然通过这个对并发编程已经有了一定的了解,但是个人认为自己目前对于java的并发编程只是局限于使用和浅层的了解阶段,在这里先对自己这段时间的学习做个总结。    首先... 查看详情

java并发编程的艺术读书笔记——java并发编程基础(代码片段)

学习参考资料:《Java并发编程的艺术》文章目录1、线程的几种状态2、如何安全的终止线程3、线程间通信(重要)3.1共享内存3.2消息传递1、线程的几种状态线程在运行的生命周期中可能处于六种状态其一:新建&#... 查看详情

java并发编程艺术系列-二java并发机制底层原理

二、Java并发机制底层原理volatilesynchronized原子操作2.1volatile原理与应用2.1.1特点轻量级的synchronized共享变量的“可见性”(定义):如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的不会引... 查看详情

java并发编程艺术系列-一并发编程问题与解决

一、并发编程问题与解决上下文切换死锁资源限制1.1上下文切换1.1.1问题CPU通过时间片分配算法来循环执行任务,当前任务一个时间片执行完后会切换到下一个任务,要保存上一个任务的状态,有一定的开销多线程不一定快-因为... 查看详情