`
herman_liu76
  • 浏览: 99591 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

java并发知识汇总

阅读更多
    项目中用到并发的地方不多,一次是多个规则线程并发校验文档,一个是多个并发监控应用,比较简单,但查找并发资料后,发现有更好的方式。于是抽空想全面了解并发,可查到网上相关并发的技术文章很多,但是对我来说有这些问题:
  • 有的只讲一个技术点
  • 有的代码多缺少理解
  • 有的层次不够深

    所以自己总结一下了,不是百科全书也不是字典,只是一定知识的索引,需要记在脑子里的。此次汇总的目标:
  • 对并发常用的东西有个概念
  • 对并发的基本例子心中有数
  • 对并发的原理,甚至设计思想有所理解

    这些汇总的东西都是需要记住的,但需要理解而不需要记太多的代码,等到用的时候能想到解决办法,能通过快速查找适当的资料来实现功能。

一、最常用的同步工具
1.synchronized是Java中的关键字,是一种同步锁。可以修饰一段代码,一个方法...这个不展开了,初级使用。

    我的理解:一个对象或者类天然有一个锁,用法:synchronized(某个对象)。这个对象也许是自己this,也可以是其它对象:private byte[] lock = new byte[0];据说这个开销比较少。有时候标识在方法前面的方法,可以理解为使用this的锁。

    这个锁的锁定与释放都由系统控制,不用自己管理。这具锁对象有wait、notify 和 notifyAll方法,用于线程之间的通讯。常用于代码结构如:for(;;){如果不满足条件就等,如果满足就执行,并通知其它等待的线程},这里注重留意一下interrupt概念,要求中断与可被中断以及中断后执行什么。暂时不展开,回头补充一下。
    最好记住一个例子,比如《thinking in java》中的一个厨师放入空盘子与顾客拿走食物的这个例子。

    后面开始是复杂的并发包中的内容。

二、并发包中常用的高级工具

2.ReentrantLock是java.util.concurrent包中的,拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。lock后必须在finally块中释放。

3.ReentrantLock与条件的配合使用
    要记住这个例子,一个篮子可以放苹果,可以有多个线程生产苹果,多个线程拿走苹果。篮子对象有个容量,而且有一个锁与两个条件。生产线程放的时候调用篮子的put方法,如果篮子满了就等待,如果不满就放个苹果,同时通知等待在【空了条件】上的拿苹果的线程可以拿了,也许通知的时候,拿苹果的线程没有等待,而是正常运行着。

    网上可以找到例子,但先记住对象关系以及锁与方法在谁身上:独立的篮子对象,生产者对象(引用篮子),消费者对象(引用篮子)。
    篮子对象上有条件锁,篮子对象提供PUT、GET方法,与条件锁有关系。而生产者与消费者不断执行线程,也就是不断调用篮子对象上的方法。
       Lock lock = new ReentrantLock();
        //条件锁与Lock是相关的
       Condition isEmptity =lock.newCondition();
       Condition isFull = lock.newCondition();

    如果是拿走的get()方法,就是先获取Lock,再判断,篮子空了就在【isEmptity】条件上等待,如果不空就拿一个,再通知可能的等待在【isFull】条件上的线程,最后释放Lock。使用get()的线程可以在循环中调用get()方法,一般一个循环中要sleep()一会。

    应用实例:之前在看阿里的数据库源工具druid中发现了使用这个条件锁的情况。上面的例子用于把握原理的代码框架,而真实的使用例子可以帮助你做自己代码时考虑的更全面。
    druid中的使用是连接池对象,它持有两个守护进程(主线程结束就可以退出JVM,不用考虑守护进程存在)。其中一个线程是产生新的连接,一个线程是删除连接。连接池有一定的容量,如果不够了就需要多的线程就产生,如果不使用的连接多了,就删除掉,维持一个最小池子,但又可以动态扩容的。

    Condition 的方法与wait、notify和notifyAll方法类似,分别命名为await、signal和 signalAll,因为它们不能覆盖Object上的对应方法。

4.ReentrantReadWriteLock
    看名字就知道是读写锁。这个用的应该蛮多的,不过估计都封闭在缓存工具里了。
    比如一个cache,持有一个map。那可以用此锁来控制对map的读写,读取数据的方法用读锁,修改数据的方法用写锁。读锁可以多个线程都获取,如果有其它线程有写的锁的时候就不行。写要等待没有读的锁,也没有其它写的锁,才能写。
  static Map<String, Object> map = new HashMap<String, Object>();
  static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
  static Lock r = rwl.readLock();
  static Lock w = rwl.writeLock();

    每个读锁或者写锁获取后都要在finally中释放。更深入的可以了解锁降级,主要是有了写锁,自己写好后,释放前再获取读锁。防止中间被其它写锁钻空子,造成自己写的内容,立马读出来又不对了的情况。

5.CountDownLatch。Java并发包中有三个类用于同步一批线程的行为,分别是CountDownLatch、 Semaphore和CyclicBarrier,这个就是其中之一。
    从名字上看就是一个倒计数的控制。有多个线程在上面等等待着,另外有多个线程会让计数减少。当减为0后,所有等待的线程就开始动起来了。(关于等待的线程怎么动,后面有提到更底层AQS里的队列。)
    多对多就不讲了,记住一对多与多对一例子。比如主线程同时启动一组线程时,主线程先持有一个CountDownLatch(1),再可以循环new出一组线程,他们会在CountDownLatch上等待。主线程sleep一定时间后(等一组都进行等待中),突然让CountDownLatch来一个countDown()。这时候一组线程就都可以动起来了。另一个情况是主线程等一组线程做完了再接着做事。主线程new一个CountDownLatch(5)后启动一组5个线程开始运行,自己在CountDownLatch上等着,每个子线程最后来一个countDown()操作,那最后计数为0时,就激活等待的主线程继续运行。

    应用实例:还是在阿里的数据库源工具druid中看到过这个,就是init的时候,主线程产生了CountDownLatch(2),而生产连接的守护线程启动后countDown(),删除连接的守护线程countDown(),表明两个需要的线程都启动了,接着做其它的事情了。生活实例:比如汽车启动时要做5个自查,每完成一个减少一个,都检查完了就正式可以开了,否则可能报警。再比如大家去吃饭,每到一个人就报数,人数够了,等待中的上菜主线程就可以启动了。而几个炒菜师傅都等着命令呢,突然一个命令,几个师傅都开始干活了。前者是主线程等子线程的条件满足后开工,后者是主线程下命令后子线程开工。

    项目实例:之前那个多规则线程校验一批文档时,最后一步都校验后要置文档已经被校验过了,正好需要些功能。其它人写的原代码比较老,这里有同时运行的线程数限制,于是先是循环检测所有线程的状态,如果有State.NEW的,并且没超过限制就start它,如果没有State.NEW了就跳出循环。再并一个循环所有的线程,就把没有State.TERMINATED的都join到主线程中来。最后又出现了与主线程的串行。如要用上面的工具就非常简单了。另外,还可以用线程池来做,当线程池shutdown后,主线程可以循环(中间sleep一会)检查线程池的isTerminated(),如果OK就可以再做后面的工作了。
   

6.Semaphore
    Semaphore与CountDownLatch相似,不同的地方在于Semaphore的值被获取到后是可以释放的,并不像CountDownLatch那样一直减到底。它也被更多地用来限制流量,类似阀门的 功能。如果限定某些资源最多有N个线程可以访问,那么超过N个主不允许再有线程来访问,同时当现有线程结束后,就会释放,然后允许新的线程进来。有点类似于锁的lock与 unlock过程。相对来说他也有两个主要的方法:
  • 用于获取权限的acquire(),其底层实现与CountDownLatch.countdown()类似;
  • 用于释放权限的release(),其底层实现与acquire()是一个互逆的过程。

    这个没有见到过例子,只能与生活中的餐馆举例。比如有5个桌子,多了顾客只能等待,少了就可以进去吃饭。有限流的功能,如果碰到类似的需要再来研究。

7.CyclicBarrier
    CyclicBarrier是用来一个关卡来阻挡住所有线程,等所有线程全部执行到关卡处时,再统一执行下一步操作,它里面最重要的方法是await()方法。
    即每个线程执行完后调用await(),然后在await()里,线程先将计数器减1,如果计数器为0,则执行定义好的操作,然后再继续执行原线程的内容。
    代码中就是先new一个CyclicBarrier(计数,统一操作)。前一个参数是多少个线程等待了就可以启动了,后一个是启动前做些其它的统一操作。生活中的场景:比如警--察抓行人闯红灯,抓住10个人(行人等待凑够数)后现场开班学习交通法规(统一操作),之后这批行人再出发。

    上面一些东西用起来也不难,关键是记住使用模型是什么样的。

三、更深入的理解java共享锁模型

8.AQS
    在java5提供的并发包下,有一个AbstractQueuedSynchronizer抽象类,也叫AQS,此类根据大部分并发共性作了一些抽象,便于开发者实现如排他锁,共享锁,条件等待等更高级的业务功能。它通过使用CAS(compare and swap,比较和交换,更底层的)和队列模型,出色的完成了抽象任务。
     仔细想想上面的那些工具,有些什么共性呢?

9.cas。compare and swap的缩写,中文翻译成比较并交换。
    CAS指令在Intel CPU上称为CMPXCHG指令,它的作用是将指定内存地址的内容与所给的某个值相比,如果相等,则将其内容替换为指令中提供的新值,如果不相等,则更新失败。这一比较并交换的操作是原子的,不可以被中断。
    这不是java特有的,而是操作系统需要保证的。利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。允许算法执行读-修改-写操作,而无需害怕其他线程同时 修改变量,因为如果其他线程修改变量,那么 CAS 会检测它(并失败),算法可以对该操作重新计算。
    作为乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,无限循环执行(称为自旋),直到成功为止,这个自旋的过程算是线程在等待吗?。AtomicInteger也是用乐观锁cas原子操作实现的。
    所以上面很多工具首先是有一个计数,而这个计数是共享的变量。比如countDown中的计数,比如Semaphore中的闸门数,比如CyclicBarrier中的阻隔数。线程调用共享变量正好用到cas。

10.等待线程队列
    另外发现上面的工具都有很多线程处于等待状态,这些线程信息必然要存下来,应该是按顺序存,而且可能如餐厅限流一样不断的产生等待和启动,所以一定用的队列这种结构。
    在countDown中通过CAS成功置为0的那个线程将会同时承担起唤醒等待线程队列中第一个节点线程的任务,而第一个节点任务又会发现自身为通知状态,又会把队列中的head指向后一个等待线程的节点,然后删除自身节点,并唤醒它。一个线程在阻塞之前,就会把它前面的节点设置为通知状态,这样便可以实现链式唤醒机制了。

11.引伸知识简单了解
    java的CAS同时具有 volatile 读和volatile写的内存语义,Java的CAS会使用现代处理器上提供的高效机器级别原子指令,这些原子指令以原子方式对内存执行读-改-写操作,这是在多处理器中实现同步的关键。同时,volatile变量的读/写和CAS可以实现线程之间的通信(CAS中的不相等时的自旋看做等待?)。把这些特性整合在一起,就形成了整个concurrent包得以实现的基石。如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式:
  • 首先,声明共享变量为volatile;
  • 然后,使用CAS的原子条件更新来实现线程之间的同步;
  • 同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。

    AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic包中的类),这些concurrent包中的基础类都是使用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类来实现的。

12.有一个疑问
    CountDownLatch、 Semaphore和CyclicBarrier的设计中,是不是可以让调用工具方法的线程由于条件不足时,都锁定在同一个对象中。而工具持有一个atomInteger的数字,而另外的线程调用工具的调整数字的方法时,如果满足条件再通知那些锁定的线程启动。这样当然不是用更底层的AQS方式来做,等于绕了一个弯而已。不过,这样可以/不可以吗?

    前面有提出等待是否底层就是自旋的问题?有文章说:
    “自旋和阻塞:像同步模式会分为两个阵营,他们的实现也会分为两个阵营:他们都采用自旋或者阻塞。自旋是一个简单的例子。比如条件同步,他采用一个一般的循环。自旋的明显缺点就是它浪费了cpu的执行周期。在一个多应用程序系统,经常会使用阻塞——让处理器去执行其他的可执行的线程。之前的线程也许会不久之后又会执行。阻塞不用不停的去查看条件和锁的状态,但是他会在来回切换程序的时候有性能花费。如果线程等待的平均时间小上下文切换时间的两倍,则可以优先考虑轮询。当每个cpu核上之后一个线程在执行的时候,轮训也是不错的选择,这通常是发生生在嵌入式或者高性能的系统中。最终我们会发现,阻塞(基于调度的同步)一定是基于轮询(自旋)实现的,因为调度器使用的数据结构本身也需要同步。”

    引伸:前阵子又看了一点andriod开发的例子,特别是对于UI线程的handlerMessage方式了解了一下。才明白为什么UI的设计都是单线程,如何处理其中耗时的操作。之前讲JS也是单线程模式,另外node.js当然也是单线程模式,NIO也是单线程模式。而其中也必然有一个LOOP环处理事件驱动的操作,而且由于是唤醒机制(FD),并不会造成CPU空转,所以也是高效的。而多线程都用于处理耗时的操作,操作是通过发送消息与主线程交互的,比如NID是注册各种事件,由选择器处理。看来很多技术的原始的思路都是相通的。


四、线程池
13.差点漏了这个重要内容了,开始写的内容比较多,考虑到本文的目的,进行精简。
    如同连接池一样,如果是比较大的开销进行生成与销毁,就要考虑一个池子。如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

    JDK中强烈建议程序员使用较为方便的Executors工厂方法Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)、Executors.newFixedThreadPool(int)(固定大小线程池)Executors.newSingleThreadExecutor()(单个后台线程)它们均为大多数使用场景预定义了设置。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。这个在业务中用的也比较多,比如定时交换数据,同步数据。有时候用spring的定时任务,也可以启动容器时用线程池,估计底层都差不多。

14.线程池配置的主要的参数
   重点是要理解核心线程数量corePoolSize 与最大线程数量maximumPoolSize的关系。线程超过核心了,先进队列。再多了就扩大线程数。如果无界队列,就永远不会用到最大数量。keepAliveTime是大于核心时,超过此时间的空闲线程要杀死。线程池实在来不及处理的时候用到handler来。
    提交给池子的线程可能放队列中缓存,有界的ArrayBlockingQueue放满了就扩大核心数。无界就一直放,不会扩大核心数。说想SynchronousQueue,你一定是碰到了假队列,它是管理直接在线程间移交信息的机制,它会直接提交给空线程做事,没有空的就建新线程,所以最大数量要设置为integer.maxvalue了吧。

15.线程池任务监控
    线程池里有一些属性可以直接看到,比较任务总数,完成的任务数之类的。但到ThreadPoolExecutor时,我们是无法知道这些任务是在什么时候才真正的执行的,为了实现这个需求,我们需要扩展ThreadPoolExecutor,重写beforeExecute和afterExecute,在这两个方法里分别做一些任务执行前和任务执行后的相关监控逻辑,还有个terminated方法,是在线程池关闭后回调(这个是否可以把主线程传进去回调整呢?),另外,我们可以通过getLargestPoolSize()和getCompletedTaskCount()来分别获取线程池数的峰值和线程池已完成的任务数。重写就是继承老的,具体查资料不展开,知道可以这样就行了。

16.线程池来不及处理的策略
   一共有四种策略为,AbortPolicy, CallerRunsPolicy, DiscardOldestPolicy, DiscardPolicy. 第一个AbortPolicy是默认策略,拒绝策略就是抛异常。这个在构造ThreadPoolExecutor时,传入一个策略对象就行了。如果传CallerRunsPolicy对象,它本身构造时还要有一个线程对象,来执行来不及处理时,你想做的事情。
   
分享到:
评论

相关推荐

    Java并发理论知识框架总结

    本知识框架是对《Java并发编程实战》一书的总结,主要围绕基本线程和线程池的使用、线程使用中出现的三大问题以及如何解决这些问题出发总结出了这个知识框架,通过该框架能更加深入的去理解Java并发理论知识。

    java 并发学习总结

    理解并熟练运用这些知识点,是成为一名合格的Java并发程序员的基础。在实际开发中,需要结合具体场景,选择合适的并发策略,以实现高性能、高并发的系统。通过不断实践和学习,可以进一步提升对Java并发编程的理解和...

    Java后端核心知识总结700页

    Java后端核心知识总结:并发编程篇 Java后端核心知识总结:MySQL篇 Java后端核心知识总结:Redis Java后端核心知识总结:RabbitMQ Java后端核心知识总结:Kafak Java后端核心知识总结:Dubbo篇 Java后端核心知识总结...

    Java并发知识网总结

    Java并发知识网:包括夯实并发基础据、玩转JUC并发工具、深入浅出底层原理

    Java并发知识体系图.png

    Java并发知识,包含Java并发编程实战,Java并发编程的艺术等书内知识的总结,、看完此图,Java并发编程即入门了

    java并发编程与高并发处理.xmind

    java并发编程总结,为xmind格式,总结的很详细,包含常见的并发容器,锁等知识

    JAVA并发编程经典书籍

    通过学习这本《JAVA并发编程经典书籍》,开发者不仅可以掌握Java并发编程的基础知识,还能深入了解并发环境下程序的运行机制,从而编写出更加高效、稳定的多线程应用。这本书对于提高Java开发者的并发编程能力具有极...

    Java并发编程全景图.pdf

    总结以上知识点,可以看出Java并发编程是一个涉及面极广的领域。从基础的线程操作到高级的并发框架,再到底层硬件和操作系统的支持,每一个部分都需要深入理解和合理应用。学习并发编程不仅是对技术的挑战,也是对...

    JAVA并发编程实战.pdf

    根据提供的文件信息:“JAVA并发编程实战.pdf”,我们可以深入探讨与Java并发编程相关的多个核心知识点。 ### Java并发编程基础 #### 1. 并发与并行 - **并发(Concurrency)**:指一个程序中存在多个执行序列(如...

    java 基础知识总结(经典)

    这篇“Java基础知识总结(经典)”涵盖了Java开发中的核心概念和重要知识点,旨在为初学者和有经验的开发者提供一个全面的回顾。以下是主要的学习点: 1. **Java环境配置**:在开始编程之前,必须安装Java ...

    Java并发编程实践(Java Concurrency in Practice) (中英版)

    通过阅读《Java并发编程实践》这本书,读者不仅可以掌握Java并发编程的基础知识,还能了解到高级并发策略和技巧,从而在实际项目中写出更高效、更稳定的并发代码。书中提供的实例和案例分析有助于加深理解,同时,...

    Java基础知识点总结.docx

    Java是一种广泛使用的面向对象的编程语言,其基础知识涵盖了多个方面,包括语法、面向对象特性、异常处理、多线程...以上只是Java基础知识的一个概述,每个话题都值得深入探讨和实践,不断学习和总结是提升技能的关键。

    Java并发编程:设计原则与模式(第二版)-3

    《Java并发编程:设计原则与模式(第二版)》是一本深入探讨Java多线程编程技术的权威著作。这本书详细阐述了在Java平台中进行高效并发处理的关键概念、设计原则和实用模式。以下是对该书内容的一些核心知识点的概述...

    JAVA核心知识点总结.pdf

    《JAVA核心知识点总结》 Java作为一款广泛应用的编程语言,其核心知识点对于开发者来说至关重要。本资料旨在为准备面试或者系统学习Java的人员提供一个全面的复习框架。以下是对其中部分关键知识点的详细阐述: **...

    java高并发

    本文档主要系统性的总结和阐述了与Java并发相关的知识点

    JAVA并发编程实践.pdf

    下面将基于标题和描述中的关键词“JAVA并发编程实践”,展开详细介绍相关的知识点。 ### Java并发编程基础 #### 1. 并发与并行 - **并发**:指的是多个任务在同一时间段内被执行(可能不是同一时刻)。 - **并行**...

    Java 并发编程实战(高清带目录).zip

    8. **并发编程最佳实践**:总结了Java并发编程中的最佳实践,帮助开发者写出更加健壮和高效的并发代码。 通过阅读《Java 并发编程实战》,开发者可以掌握Java并发编程的核心技术和实战技巧,从而在多线程环境下编写...

    java并发编程库

    总结起来,J.U.C库是Java并发编程中不可或缺的一部分,它以丰富的并发工具类、高效的执行效率和简洁的API设计,帮助开发者构建起强大的并发应用程序。通过理解J.U.C的工作原理和应用方法,开发者不仅能够编写出性能...

    java并发编程实践(第一版)

    总结而言,《Java并发编程实践》作为一本关于Java并发编程的经典书籍,通过理论讲解和实践案例相结合的方式,全面地向读者介绍了如何在Java中实现高效的并发程序设计,为开发者提供了丰富的工具和策略来处理多线程...

Global site tag (gtag.js) - Google Analytics