Java多线程番外篇

并发编程存在的挑战

  1. 上下文切换导致多线程不一定比串行更快。
  2. 死锁问题。
  3. 资源限制(硬件资源以及数据库连接数,socket连接数等软件资源)。

java中锁的几种机制

首先说明并发策略分为乐观与悲观,常见的synchronized ReentrantLock就是悲观的,因此叫互斥同步;而乐观策略是指直接执行操作,没有共享冲突,成功,产生冲突,补偿措施(重试等),因此叫非阻塞同步。
再解释什么是CAS指令(compare-and-swap):属于乐观策略,一条处理器指令,保证语义上的多个操作,实现了操作的原子性。jdk1.5之后可以使用CAS操作,JUC包中的整数原子类的一些方法就运用了CAS操作。
最后总结锁的几种机制:

  • 偏向锁:在无竞争条件下,消除整个同步,CAS也不做。锁会偏向于第一个获得它的线程,如果接下来没有其他线程获取,则该线程将永远不再同步。
  • 轻量级锁:传统的锁机制称为重量级锁,轻量级锁是指在无竞争条件下使用CAS(compare and swap)消除同步使用互斥量。
  • 互斥锁(重量级锁):synchronized ReentrantLock。
  • 自适应自旋锁:一般情况下,锁定状态很短,为了这段时间去挂起并恢复线程并不值得,因此不妨执行自旋操作,默认10次自旋。自适应是指一个“训练过程”,根据以前的锁状态决定自旋时间。
  • 公平锁与非公平锁:公平锁表示线程获取锁的顺序与加锁的顺序相同。
  • 读写锁:读读共享,其它互斥。

Java内存模型(JMM)

  • java内存模型(JMM):JMM控制java线程之间的通信,决定了一个线程对共享变量的写入何时对另一个线程可见,抽象来看,JMM定义了线程与主内存之间的抽象关系,属于语言级别的内存模型,确保在不同的编译器与处理器平台之上,禁止特定类型的编译器重排序与处理器重排序,为程序提供一致的内存可见性保证。
    java内存模型抽象结构
  • happen-before:一个操作的结果要对另一个操作可见就存在happen-before关系。它简单易懂,一个happen-before规则对应多个编译器与处理器重排序规则,屏蔽掉了程序员必须学习重排序的复杂性。
  • 重排序:是指编译器与处理器为了程序性能而对指令序列重新排序。volatile关键字本身就包含了禁止指令重排的语义;final也有其相应的指令重排规则。
    以上三者关系图
  • 延迟初始化与双向检查锁定:有时需要推迟一些高开销对象初始化的操作,用的时候在初始化。懒汉单例模式就是延迟初始化呀。这是就会出现线程安全问题,而双重检查锁定是一种典型的错误优化方式。
  • JUC包的实现原理图:
    JUC包的实现
    JUC
    ===

并发容器

ConcurrentHashMap

  • 为什么有它?为了线程安全并且要效率高。HashMap加synchronized可以实现线程安全,HashTable就是这么实现的,但是效率不好。ConcurrentHashMap使用锁分段技术,每一段数据使用一把锁,降低竞争。
  • 结构是啥?ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment使用可重入锁ReentrantLock扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。
    ConcurrentHashMap结构图
  • 怎么使用?get,put,size等方法请多看API文档。

ConcurrentLinkedQueue(无界)

  • 为什么有它?线程安全的队列有两种实现办法,一种是阻塞算法进行同步处理,另一种是非阻塞循环CAS算法,效率更好,这就是ConcurrentLinkedQueue。
  • 结构是啥?它由head节点和tail节点组成,每一个节点由item元素值和next指针组成,默认情况下item为空,tail等于head。
  • 怎么使用?请多看API文档。

阻塞队列

  • 为什么有它?常用于生产者消费者的场景,生产者是向队列中添加元素的线程,消费者是从队列中取出元素的线程,阻塞队列就作为元素的缓冲容器。
  • 结构是啥?提供阻塞的插入,移除方法。当队列满了就插入阻塞,当队列为空了就使用移除方法。
  • jdk7提供了7个阻塞队列请查看API文档。

并发框架

  • 什么是Fork/Join框架:jdk7提供的一个用于并行执行任务的框架,把一个大任务分割成若干个小任务,再汇总小任务。
  • 什么是工作窃取算法:一个线程从其它队列中窃取任务来执行。如果一个大任务分成了若干个小任务,每个几个小任务放在一个队列中,每一个队列分配一个线程去执行其中的小任务,于是干完活的线程就可以窃取其他任务,为了减少冲突,使用双端队列,从后门窃取。
  • 使用Fork/Join框架:看API文档去吧…

并发原子类

  • java.util.concurrent.atomic包中,为了提供一种用法简单,性能高效,线程安全的更新一个变量的方式。
  • 针对四种类型进行原子更新:基本类型,数组类型,引用类型,属性(字段)类型。

并发工具类

  • CountDownLatch:是join()方法的加强。join()方法的原理其实就是不停的检查join线程是否存活,如果存活,当前线程就永远等待,join线程停止,就会调用this.notifyAll(),在JVM源码中,jdk源码看不到。而CountDownLatch提供n个点,每调用一次countdown计数器减一,计数器为0时,await方法就不会阻塞当前线程了。
  • CyclicBarrier:(可循环使用的屏障)让一组线程都到达了这个屏障,然后再一起执行,它比CountDownLatch更适合复杂的业务场景,可以重新计数,并让线程重新执行一次,也可以获得被阻塞的线程数量。
  • Semaphore:(信号量)控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用公共资源,用作流量控制。
  • Exchanger:线程间协作,提供一个同步点,两个线程可以交换数据,通过exchange方法交换数据,先到达exchange的线程会等另一个线程也到达同步点,然后交换。

Executor框架(线程池学习)

Java的线程既是工作单元也是执行机制,从jdk5开始,将两者分离了,工作单元包括Runnable和Callable,而执行机制由Executor框架实现。
上五张图

  • 任务的两级调度模型:
    任务的两级调度模型

  • Executor接口的类图:其中线程池一般通过工厂类Executors来创建,它所创建的线程池都实现了ExecutorService接口。
    Executor类图

  • Executor框架使用图:其中线程池一般通过工厂类Executors来创建,所有方法都是静态方法,直接类名点调用即可。
    Executor框架使用图

  • 线程池实现原理图:
    线程池实现原理图

  • 根据上面的原理再看一下具体的类 ThreadPoolExecutor的执行示意图:(分为四步)
    ThreadPoolExecutor执行示意图
    生产者与消费者模式

  • 上面提到了生产者消费者的场景,生产者是向队列中添加元素的线程,消费者是从队列中取出元素的线程,作为第三者插足的阻塞队列就作为元素的缓冲容器,以解决生产消费不均衡的问题。纵观大多数的设计模式,都有第三者插足来进行解耦,工厂模式的第三者是工厂类,模板模式的第三者是模板类…

  • 线程池类其实就是一种生产者消费者模式的实现。而且实现方式更加高明,因为它不只是使用了一个阻塞队列,还设置了线程池的基本线程数,如果将要运行的任务数大于线程池的基本线程数才把任务扔进阻塞队列,这样处理速度更快。

发布了57 篇原创文章 · 获赞 1 · 访问量 6607

猜你喜欢

转载自blog.csdn.net/yanyingnan1357/article/details/81209811