多线程-并发编程
2020-09-27 11:09:01 0 举报
AI智能生成
多线程---并发编程
作者其他创作
大纲/内容
上下文切换
上下文切换就是一个任务从保存到加载的过程
减少上下文切换的方法
无锁并发编程:在竞争锁的时候回引起上下文的切换,可以避免使用锁,比如用hash值取模
cas算法
使用尽可能少的线程
volatile关键字
可见性
当一个线程读volatile变量时总能看到最后写入主内存的变量
当读取一个volatile变量的时候,JMM会把这个线程对应的内存设置为无效,直接从主内存进行读取
比如:当线程a在写一个变量后,之后会刷新到主内存中,而B内存的votatile变量设置为无效直接从主内存中进行读取
原子性
对于任意单个的volatile变量的读写具有原子性,但是,++这种复合操作并不具备原子性
防止指令重排序
重排序
编译器优化重排序
不改变单线程程序语义的前提下课安排语句的执行顺序
指令并行重排序
多条指令重叠执行,如果没有数据依赖性,处理器课改变语句对应的机器指令执行顺序
内存系统重排序
使用缓存和读写缓冲区,使得加载和储存可能是乱序执行的
volatile防止指令重排序主要是通过内存屏障来实现的,volatile写前后有两个内存屏障,volatile读后面有两个内存屏障,,主要防止普通写和volatile重排序
synchronize关键字
以前是一个重量级锁,现在会有一个锁升级的过程
偏向锁
偏向于第一个获得这个锁的线程,在对象头和栈中会记录锁的信息和线程的信息,当另一个线程想要或者这个锁,首先会检查对象头中是否存放了线程2如果没有,会尝试使用CAS替换mark word,失败的话会暂停获得前偏向的线程,会检查他有没有死,如果死了将对象头设置成无锁状态,如果或者,会遍历偏向对象的锁记录,栈中的锁记录和对象头的markword要么重新偏向于其他线程要么回复无锁,要么升级为轻量级锁
轻量级锁
当一个线程获取轻量级锁,会用cas替换markword,如果替换失败证明这个锁被其他线程占有,就会通过自旋的方式在获取锁,当自旋失败后锁会膨胀成重量级锁,或导致线程阻塞,当另一个获得锁的线程使用cas来替换锁的时候回失败,会释放锁并且唤醒另一个线程
重量级锁
会导致线程的阻塞,响应时间慢
但是线程不用自旋获取锁,不需要消耗cpu
死锁
互斥
这个资源只能由一个进程获得
破化互斥:不建议这样,一般加锁就是让他互斥
不可剥夺
当一个进程需要被另一个进程搜占有的资源是不可以剥夺另一个线程的资源
破化不可剥夺:在进程开始前一次分配出所有的资源
请求与保持
当一个进程请求另一个资源时,这个进程会保持自己占有的其他资源
破坏请求与保持:当一个进程申请资源遇到阻塞时,会释放他之前所保持的资源
循环等待
比如A请求B的资源,B请求C的资源,C请求A的资源,会形成一个循环结构
破化循环,可以给线程资源进行编号,依据线程编号进行资源的申请
jmm(java内存模型)
主内存
对应java堆的对象实例部分,也叫共享内存
工作内存
每个线程都有独自的工作线程,每一个线程都有一个数据的副本,线程对工作内存进行更改之后,会刷新回主内存
并不是真实的内存只是一个模型
happens-before
1.如果一个操作happens-before另一个操作,那么第一个操作将对第二个操作可见,第一个操作会在第二个操作之前执行
2.两个操作之间存在happens-before关系,并不意味这java会按照这个来执行,只要两个操作没有依赖性,第一个操作结果,对第二个操作是无所谓的就可以进行重排序
线程的通信
join()
含义:当前线程A等待Thread终止后采用thread.join返回,也就是当我们main方法中执行了这个就会先去执行调用这个join方法的线程
等待通知机制
wait()
调用的线程进入等待
notityify()
释放响应的线程
ThreadLoad的使用
主要是每个线程都对这个线程有独自的数值,是一个key-vulea形式,key对应这个线程,value对应的是这个变量的值,其实这个key是弱引用,每一层gc就会被回收,可能会出现内存泄漏的问题当线程的某个ThreadLocal对象使用完了,马上调用remove方法,删除Entry对象。
lock锁
和synchronize的不同,它是需要自己去释放搜需要调用unlock方法,他拥有了可操作性性,并且可以中断获得锁也可以超时获得锁
重入锁:ReentrantLock:表示一个线程可以对资源进程重复加锁。
线程再次获取锁:锁需要去识别获取锁的线程是否当前获取锁的线程如果是则成功
线程最终释放锁:每一次增加锁记时器加一当计数器为0时就成功释放
公平锁:根据同步队列,保证锁按照fifo的原则,代价就是需要进行大量的线程切换
非公平锁:当一个线程释放锁,队列中的都会去竞争锁,可能会造成饥饿
读写锁:ReadWriteLock
读锁:读锁是可以进行共享的,当一个资源被一个线程读的时候,其他的读操作都是可以进行的
写锁:属于独占式,当一个线程对这个资源进行写操作,任何的读写操作都是不允许对这个资源进行操作的
aqs
同步队列是一个先进先出的队列:AQS的核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,AQS就是基于CLH队列,用volatile修饰共享变量state,线程通过CAS去改变状态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒。
实现了AQS的锁有:自旋锁、互斥锁、读锁写锁、条件产量、信号量、栅栏都是AQS的衍生物
共享
跟读写锁类似
独占
流程:获取同步状图,获取成功就退出返回,获取失败,生成街边,插入到同步队列尾部,如果前驱节点是投节点就获取同步状态知道获取成功,如果不是就进入等待队列,等待被前驱节点唤醒
juc包
Atomic : AtomicInteger 原子操作类
Locks : Lock, Condition, ReadWriteLock 可重入读写锁
Collections : Queue, ConcurrentMap 并发集合
Executer : Future, Callable, Executor 线程执行池,异步Future等
任务:需要实现的接口Runnable或者Callable接口
任务的执行:执行机制的核心接口是Executor
异步的计算结果:包括接口Future和实现Future接口的FureTask类
Tools : CountDownLatch, CyclicBarrier, Semaphore 减数器,等待器,信号量
三大核心
volatile关键字,保证可见性
cas:保证原子性
AQS队列
JUC的各种功能都是通过实现自定义sync类继承AQS(AbstractQueuedSynchronizer)类,sync有公平锁,非公平锁;
线程池
线程池的核心参数
核心线程数
最大线程数
拒接策略
舍弃这一个任务
舍弃工作队列中的任务
直接抛出异常
只有调用者所在的线程来执行
线程存活的时间
线程创建的工厂
线程池的工作队列
空间线程存活时间单位
线程池的几种实现
创建一个可缓存的线程池---newCacheThreadOoll
创建可重用固定线程数量的线程池---newFixedThreadPool
创建一个定长的线程池主要特变是可周期性---newScheduledThreadPool
单线程 的线程池,支持FIFO, LIFO, 优先级策略。---newSingleThreadPool
线程池主要是为了减少线程的频繁对的创建和销毁
线程池的工作流程
首先提交任务,看核心线程池是否都在执行任务,没有就创建一个新的线程来执行任务,如果满了吧这个任务加入到工作队列中,如果队列没满插入到队列的尾部,如果队列满了判断线程池是否都处于工作状态,如果没有创建一个新的线程池来执行任务,如果满了根据不同的策略来执行
拒绝策略
直接丢弃
丢弃线程并抛出异常
丢弃队列最前面的任务,然后重新提交被拒绝的任务
由调用线程处理该任务
cas算法
是一种乐观锁的思想,有三个主要的值,内存的值预计的值和更改的值,如果预期的值和内存中的值一直就可以进行更改,当不一样就会通过自旋的方式来解决冲突
可能会出现cup使用过的,因为通过自旋的方式会很消耗cpu
可能会出现aba问题,就是其他线程已经修改之后又改回来,发现还是原来的那个值,解决方式就是通过版本号来定义
只能保证一个变量的原子操作
创建线程的几种方式
继承thread类
缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类
实现runnable接口
就是想要访问当前线程,需要调用一个方法
实现callable接口
就是一个有返回值得runnable方法,他实现的方法是call,call方法可以抛出异常,run方法不可以
我一般喜欢使用实现runnable的这种方式,因为自由度较高,而且可以使用函数式接口
ThreadLocal
提供了线程独有的局部变量,可以在整个线程存活的过程中随时取用,极大地方便了一些逻辑的实现。
0 条评论
下一页