JUC并发编程总结复盘
Posted 小样5411
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC并发编程总结复盘相关的知识,希望对你有一定的参考价值。
一、JUC并发编程进阶内容
JUC并发编程进阶内容一共写了11篇文章
https://blog.csdn.net/weixin_39615182/category_11026112.html
二、回顾(复盘)
第一篇:Synchronized和Lock区别
归纳:主要是说Synchronized和Lock都可以上锁,从而保证线程安全,但是要比较一下二者。Synchronized关键字修饰的,对应多个线程必须依次执行,如其他线程必须等待当前正在执行的线程执行完才能执行。这样执行效率不高,并且如果线程A阻塞,那么可能其后的线程就要一直等待。JUC中的Lock,用其实现类ReentrantLock进行实现,手动加锁。具体区别见该文章总结
第二篇:生产者与消费者问题
归纳:主要将两个不同版本的生产者与消费者实现,一个是Synchronized版,另一个是Lock版。Synchronized版实例就是有两个线程,生产者线程A和消费者线程B,分别循环执行Number类中的被synchronized修饰的increment()和decrement()方法,初始num=0。首先先执行increment()中业务num++,并通知线程B进行消费,线程B消费num–后再通知线程A生产,于是就会打印交替执行的结果。并且如果有多个线程一起执行呢,于是就有虚假唤醒,要用while解决(为什么替换成while?)。Lock版思想相同,就是形式和方法写法发生了变化。并且Lock中的同步监视器Condition可以精准通知和唤醒线程,主要对比二个版本写法。
第三篇:八锁现象,让你彻底理解锁
核心观点:
1、synchronized锁的对象是方法的调用者
2、static在类加载就有Class模板,锁Class
分清是否为同一把锁,同一把锁就要等待,不能插队。不属于同一把锁就互不影响。synchronized锁对象不同就属于不同的锁,static只要对应的类相同,即.class相同则为同一把锁。
第四篇:集合类不安全问题
Collection集合中有List、Set和Map三个集合。这三个中一些集合是不安全的
1、ArrayList不安全,可以由Vector,Collections.synchronizedList,CopyOnWriteArrayList三个变为安全,但Vector不推荐,是个古老的方法,JDK1.0就有了。
2、HashMap不安全,最好用ConcurrentHashMap解决,虽然工具类也能解决,如Collections.synchronizedMap(),但它主要还是基于synchronized,给所有方法加锁,也就是同一把锁,这样只能一个一个执行,效率低。而ConcurrentHashMap用的是分段锁,数据一段一段存储,锁分别锁这一段一段的数据,从而当多个线程访问不同段数据时,可以并行,大大提高效率。
3、HashSet不安全,HashSet底层是HashMap,可以由Collections.synchronizedSet变为安全,也可以由CopyOnWriteArraySet变安全(推荐),并且其底层是由CopyOnWriteArrayList实现的
第五篇:四个常用类
CountDownLatch—减法计数器
CyclicBarrier—参数达到就执行Runnable接口实现
Semaphore—并发限流
ReadWriteLock—读写锁(写入只能一个线程,读可以多个线程)
这个还是要在实战中具体应用才印象更深刻
第六篇:阻塞队列和同步队列使用
主要看这幅图
其中两个offer()和poll(),主要看是否设置等待时间,有就是超时等待,这四种情况要看具体实际,需要抛出异常就用第一个,需要有返回值就第二个,阻塞等待程序不停止,用第三个,最后需要等多少时间还不执行就停止,就选最后一个。
第七篇:线程池
我们需要知道线程池是什么?由于线程创建和销毁十分消耗资源,所以提出池化技术,线程创建后放到线程池,需要线程则到池中拿,用完后不销毁,而是放回池中,这样就能重复利用,从而减少创建和销毁线程对象的开销,并且通过线程池可以方便管理线程。
3大方法,7个参数,4种拒绝策略
3大方法主要是Executors工具类创建线程池的3个方法
Executors.newSingleThreadExecutor()
Executors.newFixedThreadPool(参数)
Executors.newCachedThreadPool()
了解即可,阿里巴巴手册说不要用Executors工具类创建,用ThreadPoolExecutor创建
7大参数
corePoolsize,线程池的基本大小
maximumPoolSize,线程池允许的最大线程数
keepAliveTime,等待时间
TimeUnit,时间单位
BlockingQueue,阻塞队列
defaultThreadFactory,线程工厂
defaultHandler,拒绝策略
4种拒绝策略
用哪种拒绝策略来拒绝任务进入
(1)AbortPolicy拒绝策略就是不处理,抛出异常
(2)CallerRunsPolicy拒绝策略就是"哪来的去哪里",就是这个属于哪个线程的就让它去找这个线程
(3)DiscardPolicy拒绝策略就是达到最大承载,不抛出异常,直接丢掉再进来的任务
(4)DiscardOldestPolicy拒绝策略会判断第一个线程是否处理完任务,如果没有,再丢掉任务,也不会抛出异常,也就是说会再努力争取一下
还提到线程池允许的最大线程数maxinumPoolSize如何确定?
答:一个是CPU密集型,任务管理器看自己电脑能同时并行多少个线程
另一个是IO密集型,确认比较消耗资源的IO操作个数,然后一般设置两倍这个数量
第八篇:ForkJoin与Stream并行流
这两个技术是应该大数据计算时的东西,ForkJoin思想就是将原来的大任务拆成多个子任务,然后子任务得出结果,最后再合并成原来的任务,ForkJoin会工作窃取,就是一个线程执行完自己的子任务,会去帮助别人执行它的任务,不让自己闲下来。
Stream并行流极大的利用CPU资源,执行效率非常高,比ForkJoin快几十倍。在大数据环境下需要想到这个方法来解决。
第九篇:一些锁的理解
公平锁、非公平锁:能否插队
可重入锁:拿到外面的锁后,就可以自动拿到里面的锁
自旋锁:while,do…while语句,不满足期望就一直循环自旋,一直到满足条件后跳出
第十篇:谈谈对Volatile的理解
主要是理解Volatile的三大特性:
(1)保证可见性
首先就要提到JMM(Java Memory Model),他是线程和内存之间关系的规范,在这种规范下,两个线程同时需要用到主存中的变量,不加Volatile那么线程A读取这个变量到其工作内存中执行,这时候线程B修改了这个变量,而线程A并不知道,用的还是之前的变量,最后又将这个变量写回,那么就相当于线程B没有进行操作了。所以需要volatile进行同步,保证可见性,线程B改,线程A也知道改了。
(2)不保证原子性
不能保证原子性,就是可能多线程执行情况下,代码也不是原子操作,线程写回内存时出现覆写操作,造成达不到目标要求。保证原子性可以用synchronized和lock,但不是最优方法,JUC包下有个专门用来保证原子性的类—atomic类,它更安全,更高效,如定义整形用AtomicInteger,自增用getAndIncrement(),这个方法是Unsafe类中的方法,它是在底层,在内存中修改值。
(3)禁止指令重排
指令重排是计算机在将源代码编译以及内存将指令重排等过程中,处理器会考虑指令之间的依赖性来重排。volatile是如何避免指令重排的,它是在不需要重排的指令之间加内存屏障,内存屏障就是cpu指令,可以禁止指令顺序的交换。
第十一篇:深入理解CAS
CAS(compareAndSet)意思是如果我期望(except)的值达到了就更新,否则就不更新,并且CAS是CPU的并发原语!也就是在底层直接通过命令执行,效率非常高。compareAndSet实现是unsafe调用compareAndSwapInt()实现的,CAS(compareAndSet)底层实际上是compareAndSwapInt,就是期望值和var5这个内存中的值比较,如果相同则vat5+var4,即var5+1。这里使用CPU原语,执行效率很高,并且do…while表示自旋锁,意思就是如果不是期望的值,就一直循环,直到是期望值才跳出。但是这会存在一个ABA问题,就是比如有两个线程,线程A期望值为1,线程B将1修改为2,然后又将2修改为1,但是线程A获取时依然是1,都不知道被修改过。原子引用(AtomicStampedReference)就出现了,它加上了一个时间戳stamp,也叫版本号,只要进行了修改,版本号就会+1,必须期望值和版本号都相等的时候才能修改。这就保证了,只要被修改,版本号就会变,版本号相同就证明没有被修改过。
第十二篇:彻底玩转单例模式
这篇主要讲了单例模式怎么实现,怎么一步一步变得安全,分饿汉式单例、懒汉式单例、内部类实现单例,懒汉式在多线程情况下会线程不安全,所以加锁,就有了双重检测锁(DCL),但是还是可以通过反射破坏,内部类实现也可以通过反射。最终解决是枚举,jdk源码中就是用枚举,反射无法破坏枚举。
以上是关于JUC并发编程总结复盘的主要内容,如果未能解决你的问题,请参考以下文章