java并发编程
2021-04-08 12:31:35 428 举报
AI智能生成
java并发编程基础总结
作者其他创作
大纲/内容
进程与线程
进程概念
线程概念
并发与并行
并发
并行
进程和线程的关系
java线程
创建和运行线程
使用Thread
使用Runnable
FutureTask
查看进程的方法
linux
ps
ps -fe 查看所有进程
ps -fT -p <PID> 查看某个进程的所有线程
top性能分析工具,显示各个进程资源占用情况
top -H -p <PID> 查看某个进程的所有线程
kill 杀死进程
windows
java工具
jps
jconsole 图像界面
jstack 查看某个进程的所有线程
线程原理
栈和栈帧
线程上下文切换
什么时候执行上下文切换
cpu时间片用完
线程自己调用了一些方法
sleep
yield
wait
join
park
synchronized
lock
垃圾回收时候
有更高优先级的线程需要被调用时候
什么时候无法完成上下文切换
程序计数器的作用
上下文切换需要保持线程状态
计数器信息
栈帧信息
局部变量
操作数栈
返回地址
线程常见方法
sleep与yield
sleep
会让当前线程由running进入time_wait 阻塞状态
其他线程是有Interupt打断阻塞,这是该线程抛出InteruptException
睡眠结束后该线程未必会立刻执行
yield
会让当前线程由running进入 runnable,并没有被阻塞
join方法
t.join 表示需等待该t线程执行完后才能执行调用t.join的线程
同步和异步
join(时间)
当t线程执行的时间 小于 时间 则按线程时间
当t线程时间 大于 时间 则按时间
join遇上interupt方法
实验一
当A线程调用t.join,A线程等待,其他线程调用A.interupt
A线程会出现中断异常
interupt()方法
尝试中断sleep join wait方法的线程
会清空打断状态,这时候变为false
会抛出InteruptException
不会中断 而是会出现异常
打断正常状态的线程
不会清空打断状态,true
打断park线程
不会清空打断状态
如果打断标记是true,park会失效
问题:在一个t1线程 如何优雅的终止t2线程
解法一 使用isInterupted,注意异常状态时候 需要设置状态标记
自己写的解法
不要使用stop,可能出现死锁
解法二:利用停止标记 使用volatile,保证该变量在多个线程的可见性
interrupted()方法
Thread调用,为true表该线程中断
如果线程中断,则调用该方法会清空中断标注
判断当前线程是否中断
isInterupted()
不会清除中断标注
wait和notify
wait
当前线程会被阻塞,等待其他线程将其唤醒并且释放锁
出现虚假唤醒
线程超时
或者其他线程调用interupt,使该线程中断
wait(n)
当超时则该函数返回
不推荐方法
stop 停止当前线程
suspend 挂起线程
resume 恢复线程
ThradLocal
保证每个线程独有一份的共享变量,且互不影响
原理
每个Thread都有两个私有变量
threadLocals
实际存储的值是在thread的threadlocal中,而不是在ThreadLocal中
本地变量一致会存在当前线程的threadLocals中,所有不需要的时候需要remove
是ThreadLocalMap 一个定制化HashMap
key 为ThreadLocal的变量引用
value为具体的值
inheritableThreadLocals
当线程第一次调用threadLocal时,才会创建在两个私有变量
InheritableThreadLocal
解决了子线程可以访问父线程的内容
ThreadLocal不支持继承性,而InheritableThreadLocal支持
主线程与守护线程
默认情况下,java进程需要等待所有线程运行完才会结束
守护线程
只要其他非守护线程结束,守护线程没有运行完也会强制结束
垃圾回收器就是一个守护线程
Tomcat中的Acceptor和poller也是守护线程,所以当tomcat接受了shutdown指令时,会立即结束 而不用等到acceptor和poller执行完再结束
线程状态
五种状态
操作系统层面
六种状态
java api层面
共享模型之管程
共享出现问题
一个线程执行i++ 另一个执行i-- 多线程操作下有可能为1或者-1或0
i++的字节码
getstatic i 获取静态变量i的值
iconst_1 准备常量1
iadd 自增
pustatici 将修改后的值存入静态变量i
单线程操作
多线程操作
临界区 Critical Section
多线程访问共享资源
只读没有问题
进行读写操作 会发生指令交错
概念:一段代码存在对共享资源进行多线程读写操作 则该代码块称临界区
静态条件 Race Condition
概念:多个线程在临界区执行,由于代码的执行序列不同 导致结果无法预测
解决方案
阻塞式解决方案
synchronized
概念
原理
解释
线程8锁案例分析
lock
非阻塞式 原子变量
变量线程安全分析
成员变量和静态变量是否安全
是否被共享
只读操作是安全
有读写操作则非安全
静态变量是否安全
局部变量是线程安全
局部变量引用的对象存在线程不安全的风险
在方法的作用范围内 是安全的
没在方法的安全范围 则存在风险
有可能出现外星方法 方法为抽象方法子类重写导致多线程共享同一个变量
常见的线程安全类
String
使用private或final的意义
保证不被继承或修改原子方法
Integer
HashTable
StringBuffer
Random
Vector
java.util.concruent包下的类
练习
卖票练习
转账练习
Monitor概念
java对象头
32位虚拟机
普通对象
数组对象
mark word结构
子主题
64位虚拟机
Monitor原理
Monitor结构
子主题
synchronized原理
字节码
synchronized进阶
轻量级锁
锁记录对象
锁膨胀
重量级锁 synchronized
使用自旋进行优化
自旋成功
自旋失败
自旋作用
偏向锁
偏向锁和轻量锁区别
轻量锁
偏向锁
偏向状态
对象头格式
说明
撤销-调用对象HashCode
撤销-其他线程使用对象
撤销-调用Wait-Notify
批量重偏向
批量撤销
子主题
锁消除
wait¬ify
原理图
原理说明
api介绍
obj.wait()
wait线程被唤醒的条件,此时wait线程阻塞
调用notify
其他线程调用该线程的interupt方法
虚假唤醒,没有被notify唤醒
线程被中断
超时
解决虚假唤醒方式
使用while循环
obj.notifyAll()
obj.notify()
模式
保护性暂停
定义
一个线程等待另一个线程的结果
实现
V1版
带时间V2版
join的原理 与V2版相似
t1.join
t1.join(millis)
多任务版
写信人 写信 同时根据收件人的邮箱编号 将信放入
收件人 创建信箱 同时等待信件送来
使用hashMap 有时会出现ConcurrentModificationException
多线程下 在遍历下删除会出现此异常
推荐使用ConcurrentHashMap
生产者和消费者
说明
jkd各种阻塞队列就是采用此种模式
消息队列
与GuardObject不同,不需要产生的结果和消费结果线程对应
示意图
实现
park&Unpark
LockSupport.park() 暂停当前线程
LockSupport.unPark(线程) 恢复该线程
顺序
先park 在unpark
park线程会发生阻塞,然后被唤醒,继续运行
先Unpark 在park
park线程不会发生阻塞
原理
组成:每一个线程都有一个parker对象
_mutex 互斥锁
_cond 条件变量 进入_cond 会发生阻塞
counter 0或者1
调用park,设置counter=0(进帐篷休息,并消耗粮食)
先检查counter是否为1(有没有粮食,有粮食才能走)
counter=0 ,则进入_cond中 线程阻塞
检查counter为1,则不需要阻塞,但counter=0(继续走路,但是会消耗粮食)
调用unpark,设置counter=1(从帐篷唤醒,并补充粮食)
线程正在运行
设置counter=1,多次调用,只有第一次有用
让线程继续运行
线程在_cond中阻塞
设置_counter=1
唤醒线程
原理图
park
unpark
unpark2
与wait¬ifyAll比较
wait需要在synchronized中 ,即需要获得对象的monitor
park属于线程的只要调用LocakSupport
必须先wait后notify
park无此要求
unpark可以精确唤醒线程
再次理解线程状态转换
new--->runnable
表示调用t.start方法
1 runnable<--->waiting
t线程用synchronized(obj)获得对象锁后
调用obj.wait方法 t线程从runnable->waiting
调用obj.notify obj.notifyAll t.interupt
锁竞争成功,t线程从 waiting->runnable
锁竞争失败,t线程从waiting->blocked
2 runnable<--->waiting
当前线程调用t.join线程,注意 是当前线程在t1线程对象监视器上等待
会从runable->waiting
当前线程结束的时候,或调用了当前线程的interupt方法
会从waiting-->runnable
3 runnable<--->waiting
LockSupport.park()
runnable-->waiting
LockSupport.unpark() 或者线程的interupt方法
runnable-->waiting
1 runnable <-->timed_waiting
t线程调用synchronized(obj),获得对象锁
t线程调用obj.wait(time)
t线程从runnable-->timed_waiting
t线程等待的时间超过time,obj.notifyAll(), t.interupt()
竞争锁成功
time_waiting-->runnable
竞争锁失败
time_waiting-->blocked
2 runnable <-->timed_waiting
当前线程调用t.join(time)
当前线程runnable-->waiting
当前线程等待的时间超过time,或者t线程运行结束,或者调用了当前线程的interupt方法
当前线程runnable-->waiting
3 runnable <-->timed_waiting
当前线程调用sleep(time)
当前线程runnable-->timed_waiting
当前线程等待时间超过time
timed_waiting--->runnable
4 runnable <-->timed_waiting
当前线程调用Locksupport.parkNonas() 或者 Locksupport.parkuntil()
当前线程从runnable--->timed_waiting
当前线程调用Locksupport.unpark(目标线程) 或者调用目标线程的interupt(),或者等待超时
目标线程 从timed_waiting--->runnable
runnable<-->blocked
t线程用synchronized(obj) 获取对象锁失败
runnable--->blocked
持有obj的线程在释放锁后,t线程会去竞争锁
竞争成功
t线程 blocked-->runnable
竞争失败
依旧blocked
runnable-->terminated
当前线程运行完毕
多把锁
可以增强并发度
如果一个线程需要多把锁 容易死锁
活跃性
死锁
一个线程需要同时获取多把锁,容易发生死锁
死锁检测
jconsole
jps检查线程 在用jstack检查死锁
linux下检查
先使用top检查cpu占用高的进程
再利用 top -Hp 进程id 来定位哪个线程
jstack 线程id
哲学家就餐问题
活锁
两个线程互相改变对方的结束条件 最终都无法结束运行
ReentrantLock
特点
可设置超时时间
可中断
可以设置公平锁
设置多个条件变量
与synchronized一样 支持可重入
可重入
可打断
公平锁
默认设置是不公平的
条件变量
synchronize的waitSet就是,当条件不满足就进入waitSet等待
demo
共享模型之内存
java内存模型 JMM
定义了主存 工作内存概念
底层对应
cpu寄存器
缓存
硬件内存
cpu指令优化
可见性
自己测试没有出现一直运行的情况
一直出现循环的原因
1 初始状态 t线程刚从主内存读取到工作内存
2 因为t线程要频繁从主内存读取,JIT编译器会将run值缓存到自己线程的工作内存的高速内存中,以减少到主内存的访问
3 1s后main线程修改了run的值并同步到主内存中,而t1线程却还是使用自己工作内存的run值
解决方式
使用volatile
可用来修饰静态变量和成员变量
强制从主内存访问 而不是访问工作内存
字节码分析
synchronized既可以保证代码块的原子性也保证可见性,就是影响性能,注意不保证指令重排
原理
CPU缓存原理
模式
终止模式之两线程终止
利用终止标记,使用volatile
使用isInterupt()
同步模式之balking
定义
实现线程安全的单例模式
有序性
指令重排
定义:jvm在不影响结果下,调准语句执行顺序
在多线程下 指令重排会出现错误
原理
指令级并行
volatile原理
内存屏障
读屏障
写屏障
如何保证可见性
写屏障
保证在该写屏障之前,对共享变量的改动都同步到内存中
读屏障
保证在该读屏障之后,都读到最新的数据
如何保证有序性
写屏障
保证写屏障之前的代码 不会重拍到写屏障之后
读屏障
保证读屏障之后的代码不会再读屏障之前
不能保证指令交错
volatile 可以禁止指令重排
happens-before
习题
线程安全单例习题
饿汉式
实现1
方法上加synchronized
double-check
使用内部类 推荐
懒汉式
枚举方式实现
枚举类
共享模型之无锁
cas与volatile
cas是原子操作
cas底层的指令架构 在单核和多核下都是原子操作
cas关键操作
原子整数
AtomicBoolean
AtomicInteger
getAndIncrement()
getAndIncrement()
getAndAdd()
getAndUpdate
getAndAccumulate
AtomicLong
原子引用
AtomicRefrence
AtomicMarkableReference
只是判断是否更改过 不需要判断改过几次
AtomicStampedReference
解决ABA问题
为什么要使用原子引用类型
例如使用BigDecimal 使用原子类型将其包装起来
原子数组
AtomicIntegerArray
AtomicLongArray
AtomicReferenceArray
原子字段更新器
利用此对某个域进行原子更新,只能给修饰了Volatile的字段进行更新
原子累加器
原子累计器的性能要比原子类型好
LongAdder和AtomicLong比较
性能提升原因
1在竞争的时候设置多个累加单元,最后将结果汇总
2 减少cas的重试失败,从而提高性能
Unsafe
概念
UnSafe 是原子类的底层实现
共享模型之不可变
使用不可变对象 来保证线程安全
时间
SimpleDateFormater
DateTimeFormater
不可变类的设计要素
类以及类的所有属性都使用final修饰
属性用final修饰 保证只读
类被final修饰 保证类不被覆盖
保护性拷贝
通过创建副本 来避免共享
享元模式
概念:
当需要重用数量有限的同一类对象时
应用
包装类
Byte Short Long 缓存都是 -128--127
Character 缓存是 0-127
Integer -128-127
最小值不能改变
最大值可以通过虚拟机参数配置
-Djava.lang.Integer.IntegerCache.high`
Boolean 缓存了 true和false
String串池
线程池
diy线程池
使用原子数组
BigDeciam
BigInteger
共享模型之工具
线程池
自定义线程池
ThreadPoolExecutor
Fork/Join
0 条评论
下一页