并发编程学习
并发编程三要素
1.原子性:全做or全不做
2.可见性:变量一改多也改
3.有序性:代码先后执行
线程五大状态
创建->就绪->运行(CPU调度)->阻塞->死亡
悲观锁:操作必加锁,线程阻塞
乐观锁:操作不加锁,失败就重试,知道成功
线程协作
-
wait
-
notify
-
notifyall
-
sleep:让出cpu使用权,不释放锁
-
yield:暂停一定时间 很少用
-
join:在start后,当join线程结束后才运行线程 当父线程需要等待子线程执行结束才执行后面内容或者需要某个子线程的执行结果会用到 join 方法
valitate关键字
- 一线程修改变量 其他线程能看见
- 程序按照代码顺序执行
synchronized关键字 - 修饰普通方法
- 修饰静态方法
- 修饰代码块
- 是一种悲观锁,性能差
线程池
corePoolSize :只有设置allowCoreThreadTimeOut 为 true,核心线程池线程数量会被销毁
maximumPoolSize:线程池最大线程数
keepAliveTime :超过此闲置时间 被销毁
workQueue:存放任务的队列 - SynchronousQueue:maximumPoolSizes一般都会设置一个最大值 Integer.MAX_VALUE,新添加任务立即执行
- LinkedBlockingQueue:大小无限制 线程池线程小于corePoolSize ,创建一个新的线程执行任务,如等于corePoolSize,就会将任务放入队列中等待
- ArrayBlockingQueue:设置队列的最大容量 线程池中线程数大于或者等于 maximumPoolSizes 的时候,就会把任务放到这个队列中 当前队列中的任务大于队列的最大容量就会丢弃掉该任务交由 RejectedExecutionHandler 处理
CAS
public class CASTest {
static int i = 0;
public static void increment() {
i++;
}
}
假如100个线程对此同时操作,很难得到100,因为i++ 这个操作,计算机需要分成三步来执行。 1、读取 i 的值。 2、把 i 加 1. 3、把 最终 i 的结果写入内存之中。所以,假如线程 A 读取了 i 的值为 i = 0,这个时候线程 B 也读取了 i 的值 i = 0。接着 A把 i 加 1,然后写入内存,此时 i = 1。紧接着,B也把 i 加 1,此时线程B中的 i = 1,然后线程 B 把 i 写入内存,此时内存中的 i = 1。也就是说,线程 A, B 都对 i 进行了自增,但是最终结果是1,不是2。
解决办法如下
public class CASTest {
static int i = 0;
public synchronized static void increment() {
i++;
}
}
加锁后很多线程竞争的花就会阻塞/唤醒这些操作,非常耗时间,尽管做了很多优化,即使优化增加了偏向锁,轻量级锁,开销依然很多。由此新生了cas的解决办法如下:
1.读取i,设为k
2.j=k+1
3.k比i,等说明没改过,加1写内存,不等说明有线程改过,那就重复步骤1
public static void increment() {
do{
int k = i;
int j = k + 1;
}while (compareAndSet(i, k, j))
}
CAS保证了原子操作 还有其他CAS原子类
- AtomicBoolean
- AtomicInteger
- AtomicLong
- AtomicReference
例子
public class CASTest {
static AtomicInteger i = new AtomicInteger(0);
public static void increment() {
// 自增 1并返回之后的结果
i.incrementAndGet();
}
}
当线程A即将要执行第三步的时候,线程 B 把 i 的值加1,之后又马上把 i 的值减 1,然后,线程 A 执行第三步,就是平常说的ABA问题,可以哟个AtomicStampedReference来控制版本控制了。接下来该讲讲java8对CAS的优化了,引入了一个cell[]数组,100个线程进行i自增擦欧总的话,分成十组,对每一个数组进行自增,10个cell元素值都为10,最后进行汇总,得到了100,同时等价于对100个线程今行了100次操作。
Lock类
CAS是AQS基石,AQS是Lock类的基石
-
独占锁
独占锁的state只有0和1,一般是通过aqs的lock方法,(state == 0)->state = 1 成功返回,失败进入阻塞队列
公平锁:锁节点按顺序假如阻塞队列 ,判断前置节点状态改变后置节点状态
非公平锁:先尝试cas,失败了进入阻塞队列。 -
共享锁
state= 0 or n 获取锁会通知所有共享类型节点
锁Lock和Condition
lock维护fairsync 和 unfairsync 两个内部类,继承自aqs
读写锁
Lock的一个子类,采用state的高16和低16代表独占锁和共享锁状态,state>1就 -1 获取读锁,如果state = 0则假如阻塞队列,一般用于读多少写的场景
并发工具类
1.countdownlatch
aqs共享模式 初始设置state为N,每有一个线程执行countdown,state-1,state=0前所有线程阻塞在队列中,state=0时唤醒队头,优先遍历算法,唤醒所有共享类型节点,执行后面代码。
2.cycliderbarrier
设置state 在某节点设置屏障,运行到此处会阻塞等待,线程到达 state -1 state = 0后唤醒condition队列的节点,执行后面代码。
3.samphere
与countlatch大同小异
4.exchanger
开辟两个空间(栈或队列),线程进来先把数据放进去,阻塞等待其他线程交换,当另一个线程进来了,那就读取这个数据,把自己数据放到对方线程格子里。 离开
同步容器
- concurrenthashmap 在java1.8改用synchronized和cas结合,性能更好一点
- concurrentlinkedlist 通过cas和synchronized实现
- concurrentSkipListMap 跳表,每层都是一个链表,每个节点可以有向下向右双指针,先向右索引,再向下细化搜索
- copyOnWriteArrayList 写时复制链表,查询不加锁,修改恢复至新list进行擦欧总,复制给原list 适合读多写少
阻塞队列
BlockingQueue - ArrayBlockingQueue 数组实现的阻塞队列,通过一个lock和两个condition实现,一个condition从对头插入节点,一个condition负责队尾读取节点。
- LinkedBlockingQueue 链表实现的阻塞队列,两个lock和对一个的condition搭配使用,因为链表可以同时对头部和尾部进行操作。
- SynchronousQueue 实现不存储数据的队列,仅保留一个队列用于保存线程节点,exchanr就是基于此设计
- PriorityBlockingQueue 支持优先级*队列,升序排列。
- DelayQueue 延时获取元素 使用上者实现,队列的元素必须实现Delayed接口,延时期满了才可以从队列中提取元素。缓存系统设计就是用DelayQueue保存元素有效期,可以定时调度某任务,TimeQueue就是基于此实现
线程池 概念: - 任务队列:线程池中维护了一个任务队列,每当向线程池提交任务时,任务加入队列。
- 工作线程:也叫worker,从线程池中获取任务并执行,执行后被回收或者保留,因情况而定。
- 核心线程数和最大线程数,核心线程数是线程池需要保持存活的线程数量,以便接收任务,最大线程数是能创建的线程数上限。
execute接口只有run,executorservice提供更多方法,如提交任务,结束线程池
抽象类abstractexecutorservice提供更多实现
最常用的个类ThreadPoolExecutor就是继承它,可以通过传入多个参数来自定义实现线程池
常用线程池
- newFixedThreadPool 设置核心线程数和最大线程数 一任务一执行,线程量达到核心线程数,放任务队列,队列满了继续开启线程处理,任务超过最大线程数限制,队列又满了,才会执行对应的拒绝策略
- newSingleThreadExecutor 单线程执行的线程池,维护一个线程,任务队列满了,线程数就剩一个,提交会执行拒绝策略
- newCachedThreadPool 一个任务进来开启一个线程,线程没执行完,再建个线程执行任务
- newScheduledThreadPool 线程和接收线程匹配时候才执行。一直提交任务,接收线程来不及处理,导致线程池不断创建线程。
- ScheduledThreadPoolExecu 内部使用delayqueue队列,内部是一个优先级队列priorityqueue,知道线程调度先后顺序和执行时间点。
以上的线程池可以通过submit提交有返回结果的callable和futuretask 通过一个future来接收结果,或者通过callable的call来写,或者execute来执行无返回值的runnable任务
Fork/Join框架
采用分治算法 大任务分成多个任务 最后Merge出最终结果 用多个线程并行计算