-
类的线程安全定义
- 如果在多线程下使用这个类,不管这个多线程如何使用和调度这个类,这个类总是表现出正确的行为,这个类就是线程安全的
- 如何线程安全
1> 操作的原子性
2> 内存的可见性 - 会在多个线程之间共享状态的时候,就会出现线程不安全
-
怎么才能做到类的线程安全
- 栈封闭
1> 定义在方法内部的局部变量,这些变量都处于栈封闭状态 - 无状态
1> 没有任何成员变量的类就叫无状态的类 - 让类不可变
1> 加final关键字。让状态不可变,对一个类,所有的成员变量是私有的,同样的只要有可能,所有的成员变量应该加上final关键字
2> 根本不提供任何可供修改成员变量的地方,同时成员变量也不作为方法返回值 - 加锁和CAS
- 安全的发布
1> 用线程安全的容器替换
2> 要么发布出去的时候,提供副本,深度拷贝 - ThreadLocal
- volatile
1> 保证类的可见性,最适合一个线程写,多个线程读的情景
- 栈封闭
-
Servlet
- 不是线程安全的类,在需求上,很少有共享的需求
- 接收到了请求,返回应答的时候,都是由一个线程负责
-
死锁:是指两个或两个以上的进程在执行过程中,由于竞争资源或者彼此通信造成一种阻塞的现象,若无外力,它无法进行下去。此时称系统出现了死锁状态
- 竞争资源多余一个,同时小于等于竞争的线程数。资源只有一个只会产生竞争激烈,不会产生死锁
- 通过系统手动解决死锁
1>jps常看死锁id
2>jstack id常看应用的死锁情况 - 有哪些死锁
1>发生死锁时,获取锁的顺序不一致。譬如两个线程A和B,需要锁定两个资源C和D,A线程依次锁定C和D,B依次锁定D和C。多次同步执行时就可能造成死锁
2>动态顺序死锁,顺序不确定。譬如两个线程A和B,需要锁定两个资源C和D,A线程依次锁定C和D,B依次锁定C和D。但是B有可能先锁定C再锁B的情况。
多次同步执行时就可能造成死锁。 - 程序避免死锁
1>解决方法1:比较hashcode,分别加锁。System.identityHashCode()获取对象的最原始的hashCode,同一个对象的一定相同
2>解决方法2:使用显示锁,尝试拿锁,tryLock -
锁的内存语义
锁的内存语义.png
-
活锁
- 使用tryLock的时候,线程之间谦让,一个线程拿到一个锁,进行操作后,再拿另一个锁的时候拿不到会退出锁。重新进入锁。
- 解决方法:添加睡眠时间,错开锁的时间
-
线程饥饿
- 一个低优先级的线程,总是拿不到执行时间
-
性能和思考
- 使用多线程的目标是为了提高性能,引入多线程后,同时会引入额外的开销.
- 衡量应用程序的性能:服务时间和延迟时间(处理速度,多快),吞吐量(处理能力的指标,完成工作的多少),可伸缩性
- 多快和多少,完全独立,甚至相互矛盾
- 对服务器应用来说,多少(可伸缩性,吞吐量)这个方面比多快更受重视
- 做应用的时候
1>先保证程序正确,确实达不到要求的时候,再提高速度
2>一定要以测试为基准 - 一个应用程序里,串行的部分是永远都有的。Amdahl定律:1/(F+(1-N)/N)。 F:必须被串行部分,程序最好的结果:1/F
-
影响性能的因素
- 上下文的切换
1>一次切换5000-10000个时钟周期,几微秒 - 内存同步
1>加锁,增加额外的指令 - 阻塞
1>挂起,包括两次额外的上下文切换
- 上下文的切换
-
减少锁的竞争
- 缩小锁的范围:对锁的持有,快进快出,尽量缩短持有的时间
- 避免多余的锁减锁的范围
- 减小锁的粒度:使用锁的时候,锁所保护的对象是多个,多个对象之间其实是独立变化的时候,不如用多个锁来一一保护这些对象。但是要注意避免发生死锁
- 锁分段(ConcurrentHashMap)
- 替换独占锁:1>使用读写锁 2>使用CAS 3>使用系统并发容器
-
单例模式
- 双重检查的懒汉式
1>安全问题:对象的引用有了,对象的域没有填充完成。
2>解决方法:对类元素添加volatile修饰符 - 饿汉式:在JVM中,对类的加载和初始化,由虚拟机保证线程安全
- 懒汉式:延迟占位模式
- 双重检查的懒汉式
网友评论