Java并发(Java复习二)

线程生命周期

主要分为5个部分,新建、就绪、运行、阻塞和结束

  • 新建后通过start方法进入就绪阶段
  • 就绪阶段通过CPU抢到执行权进入运行阶段
  • 运行阶段遇到IO阻塞,线程等待方法,wait方法或者互斥锁进入阻塞阶段
  • 阻塞阶段通过相应的解除阻塞方法,如等待时间结束,被唤醒方法和抢到锁进入就绪阶段
  • 运行阶段进入结束阶段,通过运行结束或stop方法

Java并发(Java复习二)

线程的常用实现方式

  • 通过继承Thread实现
  • 通过实现Runable接口实现
  • 通过实现Callable接口实现,可以返回返回值
    建议:使用接口方式实现线程,原因是1.继承整个Thread开销过大;2.java不可多重继承,而可以实现多个接口

线程的阻塞方式和区别及应用

  • 无限期等待(Waiting):没有CPU执行时间,需要其他线程显示地唤醒。
    • 没有设置Timeout参数的Object.wait()
    • 没有设置Timeout参数的Thread.join()
    • LockSupport.park()
    • ReentrantLock
  • 期限等待(Timed Waiting):没有CPU执行时间,在到达时间后自动地唤醒。
    • 设置Timeout参数的Object.wait()
    • 设置Timeout参数的Thread.join()
    • Thread.sleep()
    • LockSupport.parkNanaol()
    • LockSupport.parckUntil
  • 阻塞(Blocked):线程被阻塞住了,需要获取锁才能执行,相对于等待状态不是等待一定时间
    • synchronized

AQS的实现方式

AbstractQueuedSynchronizer是一个模板的抽象类,开发人员只需要实现其中的什么条件下获取同步状态,什么条件下释放同步状态的方法即可,状态值的获取设置和比较也是由抽象类实现,且通过自旋保证值的线程安全。实现后,具体的同步状态如何获取以及如何释放,则由抽象类实现,开发人员只需要调用即可。

AQS的原理

AQS获取同步:首先会通过tryAcquire方法判断是否可以获取同步,可以则直接执行,若不可以,将构造节点加入到一个双休的CLH队列中,加入后,若处于第一个节点,则会进行自旋获取同步状态,若处于后续节点,则会被阻塞在队列中(通过LockSupport.park实现,其实是等待态)
AQS释放同步:当获取了同步状态的节点释放时,会唤醒后继节点,此节点将加入到同步状态的争夺中
Java并发(Java复习二)

Java并发包基石-AQS详解

相关锁

  • synchronized和Reentrantlock

    • 区别:
    1. 使用方式不同,synchronized可在方法上和代码块中标示,而Reentrantlock只能在代码块中使用
    2. synchronized是jvm实现的而Reentrantlock是在jdk中的juc包下实现的
    3. Reentrantlock可实现公平锁和非公平锁,而synchronized只能是非公平的
    4. Reentrantlock可中断,而synchronized不行
    5. Reentrantlock可以同时绑定多个Condition对象
    • 选择:建议在不需要使用Reentrantlock的特殊功能时,使用synchronized,原因是1.synchronized是jvm自带的,可兼容各个版本,而Reentrantlock不行;2.效率上synchronized在1.6之后,进行了优化,加入了CAS自选,效率与Reentrantlock相似;3.synchronized会自动释放锁,不用担心死锁
  • 独占锁VS共享锁

    • 独占锁:只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁
    • 共享锁:多个线程可同时执行,如Semaphore、CountdownLatch、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。
  • 公平锁VS非公平锁

    • 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁。
    • 非公平锁: 当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的。
  • 乐观锁VS悲观锁(概念)=自旋锁VS互斥锁(实现)

    • 乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
    • 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
    • 两种锁的使用场景:从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
    • CAS的缺点:1.ABA问题;2.循环时间长开销大;3.只能保证一个共享变量的原子操作

线程池

  • 使用线程池的好处:
    1. 频繁创建线程会消耗资源,而从池中获取线程则节省开销,就好比吃大锅饭,每个人重新造一把勺子很耗费时间,用完了放进锅中,别人再来拿就能够节约时间
    2. 将线程的创建和使用分开,方便维护
  • 线程池执行任务顺序:
    1. 首先核心线程数量未满,则使用核心线程来执行任务,核心线程一般不会超时,除非设置
    2. 当核心任务满后,判断是否超出队列大小,队列大小未满,则加入队列,等待核心线程执行完后,去除队列任务执行
    3. 当队列满时,若未超出最大线程数量,则创建线程执行
    4. 若超出最大线程数量,则根据用户或默认的策略抛出异常拒绝任务

高并发及并发量支持

Java 内存模型