并发:交替着做事
并行:一同做事
进程:一个程序就是一个进程,进程是独立的
线程:线程为进程提供服务,多个线程可以共享数据空间,线程也有私有的空间
new:线程的新建
runable:分为两种 runable和running runable是没有时间片的
waiting:无限的等待状态,通过notify()或者notifyall()进行唤醒
time_waiting:定时的等待状态,时间结束后成为runable
blocked:线程被堵塞,线程没有得到对象的锁
terminated:线程终止
调用start方法可以使线程就绪并切换到新的线程中运行(自动调用run方法),而调用run方法还是在当前线程里
sleep方法没有释放锁,而wait方法释放了锁
sleep可以被自动唤醒,wait不加时间就一直等待
wiat()的时候调用notify()或者notifyall()会让对象从等待池中转移到锁的竞争池中
notify会唤醒随机一个等待池的线程,而notifyall会唤醒全部线程
新建Thread重写run方法就可以新建线程
通过Runable接口,可以调用Thread的构造函数新建线程
多个线程都在争夺同一个资源导致阻塞形成死锁,假如要避免死锁
调用yield方法建议线程放弃调用时间片,但可能会被忽视,不会影响锁的状态
interrupt会设置线程的中断的状态,假如线程是阻塞的,就会退出阻塞状态并抛出异常,假如是正常运行的状态会设置线程的中断表示然后继续运行
中断的处理是程序自己处理的
JVM会对指令进行重新排序,确保和未排序的运行结果一样
在不改变程序的执行结果的前提下,尽可能的为编译器和处理器优化
顺序一致性模型:按照代码的执行顺序进行执行
新建对象的一般步骤:
当重排序出现的时候,可能会将给引用赋值放在初始化对象前面,所以在多线程访问的时候会出现错误
所有对volatile变量的操作都实在内存中进行的,不会产生副本,保证了共享变量的可见性
线程在对数据操作的时候,先将数据同步到自己的本地内存中,执行结束后再同步回去,这里有一个时间差,在这段时间内,线程对副本的操作对于其他线程来说是不可见的
volatile没有Synchronized的互斥性,所以i++操作并不是原子性操作
适合一写多读的场景
前期,Synchronized属于重量级锁,JDK1.6之后进行了优化,减少了开销
Synchronized可以对类对象(静态方法和代码锁)和实例对象(实例方法)进行加锁,确保只有一个线程对对象进行操作,保证多线程对资源访问的同步
Sychronized在获得锁的时候会将该线程的本地内存置为无效,并从主内存读取.释放时会将本地内存刷新到主内存中
Sychronized是关键字,ReentrantLock是类,可以将锁对象化
两者都是可以重入的
Sychronized是非公平锁,ReentrantLock可以实现公平和非公平两种模式
Sychronized操作的是对象的markword,ReentrantLock操作的是park方法
Sychronized依赖JVM,ReentrantLock调用JDK的方法
每个线程有自己的工作内存,保存从主内存中复制的副本,属于私有空间
主内存存放共享变量
自旋锁:采用循环的方式获得锁
适应性自旋锁:根据上一次获得锁的时间调整时间进行循环获得锁
锁消除:当方法加了同步锁之后只有一个线程在使用,锁会被Java虚拟机自动消除
锁粗化:假如同一个锁被同一个线程重复申请释放,可以将请求锁的范围扩大
偏向锁:对象头的markword中存放线程的ID,减少重复申请消耗
轻量级锁:第二个线程竞争锁的时候偏向锁升级为轻量级锁
重量级锁:同一时间多个线程竞争资源
乐观锁:只是简单的检查一下和期望值是否一致,不加锁
悲观锁:对每次操作都加锁进行操作
得到对象锁的过程:
线程的私有栈帧中存储对象的markword,对象的markword更新为指向线程栈帧markword的指针,然后线程栈帧中的owner指针指向对象的markword
无锁:没有加锁
偏向锁:假如对象头MarkWord中ThreadId字段为空,则将ThreadId设为改线程的Id,表示该线程持有偏向锁,下一次该线程不会重复获得锁,假如出现锁的竞争,则偏向锁会撤销升级为轻量级锁
轻量级锁:第二个线程得到对象锁时,对象锁会变成轻量级锁,线程竞争使用CAS获得锁
重量级锁:当线程(多个)访问对象时发现对象的markword不是自己的线程栈帧,锁会膨胀为重量级锁,之后访问的线程会被堵塞,而最开始的线程会用cas自旋获得锁
可以自动的升级和降级
可见性:线程修改公共变量指令对其他线程来说是可见的
核心线程池,线程池队列,线程池
提交线程步骤:判断核心线程池的线程是否都在工作,如果否,就创建一个线程,否则就判断工作队列是否满,如果是,就存储在队列中,否则判断线程池的线程是否都在工作,如果否,就创建一个线程执行,否则调用饱和策略(对错误的处理,有4种)
线程池创建线程的时候会将工作线程封装成worker,worker会循环去执行工作队列的任务
任务的阻塞队列可以选择无限的,有限的,基于数组的,基于链表的(建议使用有界的队列,能增强系统的稳定性和预警性 )
调用execute执行实现Runnable接口的类,submit提交需要返回值的任务
线程池的shutdown和shutdownnow都是遍历线程池线程,调用中断方法中断线程,shutdownnow将线程池状态设置为stop,然后尝试停止所有正在执行的和暂停任务的线程,返回等待执行任务的列表;shutdown将线程池状态设置为shutdown,然后中断所有没有执行任务的线程
线程池的创建:
获取内存中的值,和期望的值作比较是否一致,假如一致就将新值赋值,不一致就重试
CAS会出现ABA的问题,无法知道中途的值(解决 AtomicStampedRefence)
CAS适用于写比较少的情况下(多读场景,冲突一般较少),synchronized适用于写比较多的情况下(多写场景,冲突一般较多)
https://www.cnblogs.com/waterystone/p/4920797.html
https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html
https://javadoop.com/2017/06/16/AbstractQueuedSynchronizer/
https://www.javadoop.com/post/AbstractQueuedSynchronizer-2/
https://www.javadoop.com/post/AbstractQueuedSynchronizer-3
AQS主要是更改state的数值
有两种资源的共享模式exclusive(Reentrantlock)和share(Countdownlatch/Semaphore)
tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
acquire -> if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// tryAcquire(arg)没有成功,这个时候需要把当前线程挂起,放到阻塞队列中。
tryacquire(arg) (获得锁 1.无人持有直接cas 2.有人拿了,看是否是同一个线程)
addWaiter(Node.EXCLUSIVE) 先是直接添加到阻塞队列的尾部(尾部不是空)使用cas
如果尾部为空或者cas出错,就用enq(node)添加(会先创建一个初始化的head(假如空的话),然后添加到他的后面)
acquireQueued(进入阻塞队列,判断是第一个还是其他位置)如果是第一个,若前驱是head,就使用tryacquire竞争一下锁
如果是其他位置,将前驱节点的waitstate设置为-1,如果大于0,就向前找小于等于0的waitstate,然后挂起
release->获得state,将state-1 如果state等于0就释放(将head的waitstate=0,从后向前找waitestate<=0的点(一直向前找),找到后唤醒(unpark(node)唤醒后的node从park的地方起来),再set一下state
Reentranlock分为公平锁和非公平锁,非公平锁的当前线程调用lock的时候直接用cas抢锁,抢不到后和公平锁一样请求锁acquire
在调用tryacquire的时候又会使用cas抢,然后再和之前一样去阻塞队列排队
condition基于reentranlock实现,await和signal都要获得锁
conditon的await()响应中断->addcondition()将节点添加到条件队列,插入到尾部(先得到tail的node,清除非condition的状态节点(unlinkcancelledwaiter(普通单链表的操作)),然后在初始化node并添加到条件队列(入队了)
之后完全释放锁(直接把state=0,返回赋值前的值)->(isOnSyncQueue(判断条件是否是CONDITION&&node.next!=null&&并在阻塞队列中找是否有这个元素))如果都没有,等待进入阻塞队列(挂起)(其他线程可以来获得锁)
signal(要获得当前线程的独占锁)将要转移的node(一般是第一个,也有可能第一个取消了(通过cas判断waitstate))与条件队列断绝关系,使用enq加入阻塞队列(state会变成0),如果阻塞队列中node前驱节点放弃了锁或者cas前驱节点的waitstate(cas signal)失败了就唤醒(unpark)节点
node唤醒的情况
node被唤醒之后,会checkinterruptwhilewaiting 判断是否发生了中断而且如果发生中断是发生在signal前或者signal后(transferAfterCancelledwait 通过cas设置state判断),中断唤醒后如果发现不是被signal的(被signal的node waitestate会变成0,node通过cas检查)会主动加入到阻塞队列中enq
加入阻塞队列中,准备获得锁,acquirequeue(node,savedstate)并判断是否发生中断,有中断就进行处理(程序处理,例如抛出或者不抛)
将state设为n,使用countdown() 减少1,state=0的时候就唤醒调用await()的方法
await()->tryacquire(判断state是否=0来返回1,-1)
返回-1 ->doacquiresharedinterruptibly 先入队,将前驱节点waitstate设置为-1,挂起(可以重复多次入队挂起 数量等于n)
countdown()->tryreleaseshared(不断减少state)如果=0,就调用doreleaseshared方法(state=0)
第一个线程被doreleaseshared()里面的unparksuccess唤醒之后,由于state=0,循环在第二轮的时候>tryacquire返回1,线程被调用setheadandProgate(node,r),设置为头结点(state为0)并唤醒下一节点调用doreleaseshared()
基于condition实现,只有当每个线程await()了之后,使用generation开始下一次的新生,另外在执行过程中有中断 超时或者要做的action出现异常 栅栏就会break,所有await的线程会notifyall然后抛出异常
分公平策略和非公平策略,state表示资源的数量,state=0表示需要没有资源了,要等release,其他的和共享的aqs差不多,通过判断资源的数量(cas)来阻塞线程
假如一个变量被ThreadLocal修饰,那么每一个线程在访问这个变量的时候会保存到本地副本中,并且会存放在线程的ThreadLocalMap中,key为ThreadLocal对象
用原子的方式更新数据
有4类原子类:
使用CAS和volatile变量和native方法实现原子操作,避免使用Sychronized