java并发编程之美----学习笔记总揽1

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lwglwg32719/article/details/86510292

并发和并行
1.并发是指同一时间段内多个任务同时在执行,且都没有执行结束,而并行是指在单位时间内多个任务同时在执行。并发任务强调的是一个时间段内同时在执行,而一个时间段是由多个单位时间累积而成,所以说并发的多任务在单位时间内不一定同时执行。在单CPU的时代多个任务都是并发执行的,这是因为单个CPU同时只能执行一个任务
2.而在多线程编程实践中,线程的个数往往大于CPU的个数,所以一般都称多线程并发编程而不是多线程并行编程。
3.由于java中的线程是与操作系统的原生线程是一一对应的,所以当阻塞一个线程时,需要从用户态切换到内核态执行阻塞操作,这是很耗时的操作,而synchronized的使用就会导致上下文切换。
4.synchronized的一个内存语义,这个内存语义就可以解决共享变量内存可见性问题。进入synchronied块的内存语义就是把在synchronied块内使用到的变量从线程的工作内存中清除,这样在synchronized块内使用到该变量时就不会从线程的工作内存中获取了,而是直接从主内存中获取。推出synchronized块的内存语义就是把synchronized块内存对共享变量的修改刷新到主内存。除了结局共享变量可见效问题外,synchronized经常被用来实现原子性操作
5.volatile不会带来线程上下文的切换开销。而且volatile不保证原子性
6.java指令重排序,java内存模型允许编译器和处理器对指令重排序以提高运行性能,并且只会对不存在数据依赖性的指令重排序。写volatile变量时,可以确保volatile写之前的操作不会被编译器重新排序到volatile写之后。读volatile变量时,可以确保volatile读之后的操作不会被编译器重排序到读之前
7.伪共享---缓存一致性。缓存与内存交换数据的单位就是缓存行
8.悲观锁指对数据被外界修改持保守态度,认为数据很容易就会被其他线程修改,所以在数据被处理前先对数据进行加锁,并在整个数据处理过程中,使数据处于锁定状态。乐观锁是相对悲观锁来说的,它认为数据在一般情况下不会造成冲突,所以在访问记录之前不会加排他锁,而是在进行数据提交更新的时候,才会正式对数据冲突与否进行检测
9.弱一致性是指返回迭代器之后,其他线程对list的增删改对迭代器是不可见的
10.ConcurrentLinkedQueue是线程安全的无界非阻塞队列,其底层数据结构使用单向链表实现,对于入列和出队操作使用CAS来实现线程安全。每个元素被包装成一个node节点。队列是靠头、尾节点来维护的,创建队列时头、尾节点指向一个item为null的哨兵节点。第一次执行peek或者first操作时会把head指向第一个真正的队列元素。由于使用非阻塞cas算法,没有加锁,所以在计算size时有可能进行了offer、poll或者remove操作,导致计算的元素个数不精确,所以在并发情况下size函数不是很有用
11.独占锁实现的阻塞队列LinkedBlockingQueue。内部是通过单向链表实现的,使用头、尾节点来进行入列和出列操作,也就是入列操作都是对尾节点进行操作,出队操作都是对头节点操作。对尾、头节点的操作分别使用了单独的独占锁从而保证了原子性,所以出堆和入队时可以同时进行的。另外对头、尾节点的独占锁都配备了一个条件队列用来存放被阻塞的线程,并结合入队、出队操作实现了一个生产消费模型
12.有界数组实现的阻塞队列arrayblockingqueue。arrayblockingqueue使用全局独占锁实现了同时只能有一个线程进行入列和出队的操作,这个锁的粒度比较大,有点类似于在方法上添加synchronized的意思,其中offer和poll操作通过简单的加锁进行入列 出列操作,而put take操作则使用条件变量实现了,如果队列满则等待,如果队列空则等待,然后分别在出队和入队操作中发送信号激活等待线程实现同步。另外相比linkedblockingqueue,arrayblockingqueue的size操作的结果是精确的
13.priorityblockingqueue是带有优先级的无界阻塞队列,每次出队都返回优先级最高获赠个最低的元素。其内部是使用平衡二叉树堆实现的,所以直接遍历队列元素不保证有序。默认使用对象的compareto方法提供比较规则,也可以自定义comparator。队列内部使用二叉树堆维护元素优先级,使用数组作为元素存储的数据结构,这个数组是可扩容的。当当前元素个数>=最大容量时会通过CAS算法扩容,出队时始终保证出队的元素是堆树的根节点,而不是在队列里面停留时间最长的元素
14.delayqueue并发队列是一个无界阻塞延迟队列,队列中的每个元素都有个过期时间,当从队列获取元素时,只有过期元素才会出队列。队列头元素是最快要过期的元素。其内部使用了priorityqueue存放数据,使用reentrantLock实现线程同步,另外队列里面的元素实现delayed接口其中一个是获取当前元素到过期时间剩余时间的接口,在出队时判断元素是否过期了,一个是元素之间比较的接口,因为这是一个有优先级的队列
15.线程池主要解决了两个问题:一是当执行大量异步任务时线程池能够提供较好的性能。在不使用线程池时,每当需要执行异步任务时直接new一个线程来运行,而线程的创建和销毁是需要开销的。线程池里面的线程是可复用的,不需要每次执行异步任务时都重新创建和销毁线程。二是线程池提供了一种资源限制和管理的手段,比如可以限制线程的个数,动态新增线程等。每个threadpoolexecutor也保留了一些基本的统计数据,比如当前线程池完成的任务数目
16.RUNNING : 接受新任务并且处理阻塞队列里的任务。
SHUTDOWN :拒绝新任务但是处理阻塞队列里的任务。
STOP :拒绝新任务并且抛弃阻塞队列里的任务,同时会中断正在处理的任务。
TIDYING : 所有任务都执行完(包含阻塞队列里面的任务)后当前线程池活动线程
数为0 , 将要调用terminated 方法。
TERMINATED : 终止状态。terminated 方法调用完成以后的状态。
17.线程池参数如下。
• corePoolSize :线程池核心线程个数。
• workQueue :用于保存等待执行的任务的阻塞队列,比如基于数组的有界
ArrayBlockingQueue 、基于链表的无界LinkedBlockingQueue 、最多只有一个元素的
同步队列SynchronousQueue 及优先级队列PriorityB lockingQueue 等。
• maximunPoolSize : 线程池最大线程数量。
• ThreadFactory :创建线程的工厂。
• RejectedExecutionHandler :饱和策略, 当队列满并且线程个数达到maximunPoolSize
后采取的策略, 比如AbortPolicy (抛出异常〉、CallerRunsPolicy (使用调用者所在
线程来运行任务) 、DiscardOldestPolicy (调用poll 丢弃一个任务,执行当前任务)
及DiscardPolicy (默默丢弃,不抛出异常〉
• keeyAliveTime :存活时间。如果当前线程池中的线程数量比核心线程数量多,并且
是闲置状态, 则这些闲置的线程能存活的最大时间。
• TimeUnit : 存活时间的时间单位。
19.线程池巧妙地使用一个Integer类型的原子变量来记录线程池状态和线程池中的线程个数。通过线程池状态来控制任务的执行,每个worker线程可以处理多个任务。线程池通过现场的复用减少现场创建和销毁的开销
20.fixed-delay类型的任务的执行原理为,当添加一个任务到延迟队列后,等待initialdelay时间,任务就会过期,过期的任务就会被从队列移除,并执行。执行完毕后,会重新设置任务的延迟时间,然后再把任务放入到延迟队列中,循环往复。需要注意的是,如果一个任务在执行中抛出了异常,那么这个任务就结束了,但是不影响其他任务的执行。
21.countdownlatch相比使用join方法来实现线程之间的同步更具有灵活性和方便性。他是使用aqs(abstractqueuesynchronizer抽象的队列式的同步器),使用aqs的状态变量来存放计数器的值,首先在初始化countdoenlatch时设置状态值(计数器值),当多个线程调用countdown方法时实际是原子性递减aqs的状态值。当线程调用await方法之后当前线程会被放入到aqs的阻塞队列中等待计数器为0再返回。其他线程用countdown方法让计数器值递减1,当计数器值变为0时,当前线程还要调用aqs的doreleaseshared方法来激活由于调用await方法而被阻塞的线程
22.Timer:TaskQueue是一个由平衡二叉树实现的优先级队列,每个Timer对象内部有一个TaskQueue队列。用户线程调用Timer的schedule方法就是把TimerTask任务添加到TaskQueue队列。在调用schedule方法时,long delay参数来指明该任务延迟多少时间执行。TimerThread是具体执行任务的线程,他从TaskQueue队列中获取优先级最高的任务进行执行。需要注意的是,只有执行完了当前的任务才会从队列里获取下一个任务,而不管队列是否有任务已经到了设置的delay时间。一个Timer只哟一个TimerThread线程,所以可知Timer内部实现是一个多生产者-单消费者模型
23.当任务在执行过程中抛出InterruptedException 之外的异常时,唯一的消费线程就会因为抛出异常而终止,那么队列里的其他待执行的任务就会被清除。所以在TimerTask 的run 方法内最好使用町-catch 结构捕捉可能的异常,不要把异常抛到run 方法之外。其实要实现Timer 功能,使用ScheduledThreadPoo!Executor 的s chedule 是比较好的选择。如果ScheduledThreadPoolExecutor 中的一个任务抛出异常,其他任务则不受影响。
24.ScheduledThreadPoo!Executor 是并发包提供的组件, 其提供的功能包含但不限于Timer。Timer 是固定的多线程生产单线程消费, 但是ScheduledThreadPoolExecutor 是可以配置的, 既可以是多线程生产单线程消费也可以是多线程生产多线程消费,所以在日常开发中使用定时器功能时应该优先使用ScheduledThreadPoo!Executor 。
25.总结: ThreadLoca!Map 的Entry 中的key 使用的是对ThreadLocal 对象的弱引用,这在避免内存泄漏方面是一个进步,因为如果是强引用, 即使其他地方没有对ThreadLocal 对象的引用, ThreadLoca!Map 中的ThreadLocal 对象还是不会被回收, 而如果是弱引用则ThreadLocal 引用是会被回收掉的。但是对应的value 还是不能被回收,这时候ThreadLocalMap 里面就会存在key 为null 但是value 不为null 的entry 项, 虽然ThreadLocalMap 提供了set 、get 和remove 方法, 可以在一些时机下对这些Entry 项进行清理,但是这是不及时的, 也不是每次都会执行,所以在一些情况下还是会发生内存漏, 因此在使用完毕后及时调用remove 方法才是解决内存泄漏问题的王道。
总结:如果在线程池里面设置了ThreadLocal 变量,则一定要记得及时清理,因为线程池里面的核心线程是一直存在的,如果不清理,线程池的核心线程的threadLocals 变量会一直持有ThreadLocal 变量。

猜你喜欢

转载自blog.csdn.net/lwglwg32719/article/details/86510292