JUC编程归纳

48 篇文章 0 订阅
21 篇文章 0 订阅

主要关注concurrent atomic locks function包

并发和并行是不一样的 并发主要目的是为了充分地利用单核CPU的资源 而并行是在多核CPU下的概念

java线程状态 :new runnable blocked waiting(等待) time_waiting(延迟等待) terminated

waiting:不占有锁 不占有cpu资源
sleep:占有锁 不占有cpu资源
blocking:占有锁 不占有cpu资源(sleep也就是进入blocking状态  io阻塞)

lock锁
关于lock和syncronsized的归纳这里我之前也有 下面主要写一些使用方法:
https://blog.csdn.net/weixin_40695328/article/details/104069459

(1)retranlock:
	需要手动lock unlock
	trylock尝试获取锁 获取不到就结束等待 可以用来解决synchronized阻塞时候死锁的问题
(2)虚假唤醒问题: 用where不要用if
(3)		synchronized用wait notify     
					lock 用condition对象的 await signal
					condition的好处就是可以创建同一个lock对象锁下的多个condition 来完成精确的唤醒!

LIst:

大家都知道arraylist线程不安全,容易出ConcurrentModificationException:迭代的时候同时对其进行修改
(1)vector是线程安全 但是效率低

(2)可以用Collections.syncronsizedList(new ArrayList()) 效率一样低:
		根据是否实现RandomAccess这个接口来返回的SynchronizedRandomAccessList还是SynchronizedList.   而因为arraylist实现了 SynchronizedList 所以用的是SynchronizedList :基本上也是暴力加锁
		

(3)最好用 copyonwriteArrayLIst 效率高
				用指针完成写入时复制 来提高效率
				同理可以用 copyonwriteSet   concurrentHashmap(注意根据实际的业务设置初始值)

callable

跟其他两个runnable thread的区别:有返回值 会抛异常
需要使用FutureTask(Callable) 实现了RunnableRutue继承了Runnable     (桥接模式)  而FutureTask就是适配类
会有结果缓存!  (开多个线程执行同一段代码 后面的线程不会再执行)
获取值的方法会阻塞 等待结果返回后后才执行

讲一些辅助类

(1)减法计数器countDownLAtch(初始值)
	      countDownLAtch.countDown 每一次减少一个值 
		在达到初始值设置的后 就可以继续执行 await处的代码 否则await处是阻塞的
(2)加法计数器CyclicBarrier(初始值,达到初始值后开始运行的线程runnable)
		调用await方法 线程等待    等到等待的数量达到了初始值 设置的runnable才会继续进行 相对应的其他await线程结束 
(3)semaphone信号灯(初始值):初始值代表了一次性能进入acquire()方法的线程有多少:控制同一时刻能有多少线程进入acquire方法执行

读写锁

ReentranreadWirteLock进行读写分离
写锁:调用witreLock。lock   记得unlock释放
读锁:readlLOck.lock 同样记得释放
读写分离 写入只能单线程 读取可以并发

同步队列

synchronousQueue 同步队列  
队列是空的 不会存储元素 每次put操作 要等待下一个take 否则无法继续添加元素
比ArrayBlockingQueue轻量 可以用在线程间传递单一资源使用

阻塞队列

(1)加入满的队列会阻塞 超过初始值抛出IllegalStateException
(2)取出空的队列会阻塞 没内容再取抛出nosuchelementException
BlockingQueue---ArrayBlockingQueue(初始值) linkedBlockingQueue
add remove会抛出异常   element检测队首的元素
offer(元素,时间,TimeUNit枚举类属性) poll(元素,时间) 不会抛出异常会返回特殊值 一直等待直到超时推出  插入不进去返回false   peek检测队首元素没有值返回null
put take 一直阻塞

线程池

Executors  可以管理线程的总数
		----new\FIxedThreadPool 固定数量   			execute方法放入runnable线程进行执行 注意关闭线程 同时只有初始值的线程一起执行
		----newCachedThreadPool 可以动态弹性增减
		----newsingleThreadExecutor 只有一个
		----newScheduledThreadPool 定长线程池 支持定时和周期性执行任务

底层都是调用ThreadPoolExecutor 只是参数的不同  项目中要使用这个类去自定义创建 不能用executors 因为允许的队列长度是Integer.MaxValue 一定会oom
corePoolsieze 核心池的大小
maximunpoolsize 核心池最大有多少:只有在队列已满的情况下才会使用到maximunpoolsize - corePoolsieze的线程 因为线程池是优先使用队列的 如果创建队列的时候不指定初始值而是无限大 就会导致maximunpoolsize无法使用 全部都是corePoolsieze来处理 直到队列满了后maximunpoolsize才会生效使用
keepalivetime  空闲线程的存活时间:指的是maximunpoolsize - corePoolsieze的线程
timeunit时间单位
workqueue队列
threadFactory 创建线程使用 一般不必传参
 rejectexecutionhandler 拒绝策略:超过maximunpoolsize+workqueue队列长度的线程怎么处理的问题
 			----abortpolicy  :默认 队列满了就丢弃任务抛出异常 
 			---callerRUnspolicy: 哪个线程调用的返回给哪个线程 例如是主线程开启的 就返回给主线程来执行
 			----discardOldestPolicy:尝试进入队列 方法是尝试删除最早进入队列的任务 不一定成功
 			----discardPolicy:队列满了也会丢弃 不抛出异常

怎么设置maximunpoolsize:可以根据不同的任务类型来设置线程池 最好准备两个版本
(1)根据Runtime.getRuntime().availableprocessors判断cpu核数 来创建并发数量 ---- cpu密集型
(2)磁盘读写 io线程数比较多的时候 应该设置最够多的线程数量 不让cpu空闲下来 目的还是为了提高cpu使用效率 ----io密集型

四大函数是接口:只包含一个抽象方法的接口

(1)Function    apply 传入T返回R
(2)Predicate test 传入T返回boolean
(3)Consumer accept 传入T没有返回值
(4)Supplier get 没有参数传入返回T

Stream流式计算

主要关注 stream filter map sort limit 

stream并行流效率很块! 底层也是Forkjoin

分支合并:

Forkjoin:将一个比较大型的任务切分成小块 再将小块的结果合并
工作窃取:利用双端队列  A任务完成后 比B快的话 会从B的尾巴拿取任务继续执行 而Forkjoin的意义就在于这里 可以而不像Future调用get后就会阻塞 加大效率和线程的利用率
类:通过ForkJoinPool  workqueue是其内部类 也就是队列,并且存在于每一个线程(ForkJoinThread)当中 
	   ForkJoinTask是正在ForkJoinPool中运行的任务 一般使用(recursionTask有返回值    recursionAction没返回值   使用方式继承即可)
	   		---fork 创建子任务   在递归方法调用后调用相应的fork方法    在返回的时候返回join
	   		---join 任务完成后获取计算结果  在递归方法返回的时候调用
	   		----invoke 开始执行 计算没有完毕就等待    ForkJoinPool的方法 传入ForkJoinTask接口
	 

使用于cpu密集型而不是io密集型

异步回调

completableFuture
runAsync方法执行没有结果       supplyAsync有返回结果 get回取结果   whenComplete正常编译完成 exceptionally异常情况
主要好处就是发送任务后直到获取返回值前不阻塞

JMM

jmm主要跟volitaile 和 主内存工作线程有关 
volitaile 轻量级同步机制
(1)保证可见性质
(2)不保证原子性    可以用atomic类来完成(轻量级 通过自旋+cas完成 不进行内核态操作 但是因为自旋所以适合在低并发场景使用)
(3)**禁止指令重排序**
					--利用内存屏障 保证特定变量的线程安全和内存可见性
					而synchronized只能保证块与块之间的顺序 无法保证锁的代码块内部非原子操作的指令重排序

单例模式

主要关注三个问题 
		代码层面怎么防止多例
		序列化反序列化
		反射
		
(1)饿汉式 线程安全
(2)懒汉式 线程不安全 一般使用双重if判断 即使如此 同步代码块内的创建对象的方法也有指令重排序的问题		
		指令重拍:分配内存  构造方法创建对象 栈区引用指向实际的内存对象     可能先第三步 导致返回对象为null      所以懒汉式建议加volitaile			
		需要在private构造器中再加一个判断防止反射后再调用构造器问题  但是还是不能避免反射的问题
(3)静态内部类
(4)以上并不能禁止反射导致的问题!!      最好的就是用枚举
				枚举的好处:
						没有无餐构造器!!!通过反编译可以知道
						枚举序列化只是序列化了name属性  可以防止序列化反序列化导致的单例破坏问题 并且编译器是不允许任何对这种序列化机制的定制的
						 枚举无法通过反射进行创建 因为在newInstance方法里面写明了针对枚举类型的逻辑 直接抛出异常(调用的是两个参数的构造器)

				

CAS

atomac中:
原子性  内存级别的  所以效率高
CAS的核心类: Unsafe    java可以用它来直接操作内存
cas是cpu的并发源语:
			首先获得传入的对象offset位置的值    然后在获取新值跟这个值是否一样 一样就做你的数据操作
		
	比较工作内存中的值和驻内存的值 相同执行操作 否则一直循环比较到值一致为止

缺点:  自旋循环时间长 开销大 
			只能保证一个共享变量的原子操作
			ABA问题!!:取出交换数据的期间可能被变动了 只是最终的结果一致

解决ABA:
		原子引用atomicStampedReference   用版本号 来控制(乐观锁)

atomicReference引申:
		自旋锁 底层原理是CAS   利用atomicReference的compareAndSet方法判断是否是null并且可以获得锁 可以的话把相应的线程赋值 意味着我这个线程已经拿到了这个锁   解锁相反判断是否是我这个线程 是就置为空  其他线程的调用过程同理

死锁解决

jps -l拿到进程号 
jstack 进程哈   分析指定线程的堆栈信息
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值