1 类的线程安全定义。怎么才能做到类的线程安全?
类的线程安全定义
如果多线程下使用这个类,不过多线程如何使用和调度这个类,这个类总是表示出正确的行为,这个类就是线程安全的。
类的线程安全表现为:
- 操作的原子性
- 内存的可见性
不做正确的同步,在多个线程之间共享状态的时候,就会出现线程不安全。
1.1 栈封闭
所有的变量都是在方法内部声明的,这些变量都处于栈封闭状态。
1.2 无状态
没有任何成员变量的类,就叫无状态的类
1.3 让类不可变
让状态不可变,两种方式:
- 1,加final关键字,对于一个类,所有的成员变量应该是私有的,同样的只要有可能,所有的成员变量应该加上final关键字,但是加上final,要注意如果成员变量又是一个对象时,这个对象所对应的类也要是不可变,才能保证整个类是不可变的。
- 2、根本就不提供任何可供修改成员变量的地方,同时成员变量也不作为方法的返回值
1.4 volatile
保证类的可见性,最适合一个线程写,多个线程读的情景
1.5 加锁和CAS
1.6 安全的发布
类中持有的成员变量,特别是对象的引用,如果这个成员对象不是线程安全的,通过get等方法发布出去,会造成这个成员对象本身持有的数据在多线程下不正确的修改,从而造成整个类线程不安全的问题。
1.7 TheadLocal
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
PS:Servlet
不是线程安全的类,为什么我们平时没感觉到,:
- 1、在需求上,很少有共享的需求。
- 2、接收到了请求,返回应答的时候,都是由一个线程来负责的。
2 性能和思考
使用并发的目标是为了提高性能,引入多线程后,其实会引入额外的开销,如线程之间的协调、增加的上下文切换,线程的创建和销毁,线程的调度等等。过度的使用和不恰当的使用,会导致多线程程序甚至比单线程还要低。
衡量应用的程序的性能:服务时间,延迟时间,吞吐量,可伸缩性等等,其中服务时间,延迟时间(多快),吞吐量(处理能力的指标,完成工作的多少)。多快和多少,完全独立,甚至是相互矛盾的。
对服务器应用来说:多少(可伸缩性,吞吐量)这个方面比多快更受重视。
我们做应用的时候:
1、先保证程序正确,确实达不到要求的时候,再提高速度。(黄金原则)
2、一定要以测试为基准。
一个应用程序里,串行的部分是永远都有的。
Amdahl定律 : 1/(F+(1-N)/N) F:必须被串行部分,程序最好的结果, 1/F。
3 影响性能的因素
3.1 上下文切换
是指CPU 从一个进程或线程切换到另一个进程或线程。一次上下文切换花费5000~10000个时钟周期,几微秒。在上下文切换过程中,CPU会停止处理当前运行的程序,并保存当前程序运行的具体位置以便之后继续运行。从这个角度来看,上下文切换有点像我们同时阅读几本书,在来回切换书本的同时我们需要记住每本书当前读到的页码。
上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。
内存同步
一般指加锁,对加锁来说,需要增加额外的指令,这些指令都需要刷新缓存等等操作。
3.2 阻塞
会导致线程挂起【挂起:挂起进程在操作系统中可以定义为暂时被淘汰出内存的进程,机器的资源是有限的,在资源不足的情况下,操作系统对在内存中的程序进行合理的安排,其中有的进程被暂时调离出内存,当条件允许的时候,会被操作系统再次调回内存,重新进入等待被执行的状态即就绪态,系统在超过一定的时间没有任何动作】。很明显这个操作包括两次额外的上下文切换。
4 如何优化线程的性能
4.2 减少锁的竞争
4.3 减少锁的粒度
使用锁的时候,锁所保护的对象是多个,当这些多个对象其实是独立变化的时候,不如用多个锁来一一保护这些对象。但是如果有同时要持有多个锁的业务方法,要注意避免发生死锁
4.4 缩小锁的范围
对锁的持有实现快进快出,尽量缩短持由锁的的时间。将一些与锁无关的代码移出锁的范围,特别是一些耗时,可能阻塞的操作
4.5 避免多余的缩减锁的范围
两次加锁之间的语句非常简单,导致加锁的时间比执行这些语句还长,这个时候应该进行锁粗化—扩大锁的范围。
4.6 锁分段
ConcurrrentHashMap就是典型的锁分段。
4.7 替换独占锁
在业务允许的情况下:
1、使用读写锁,
2、用自旋CAS
3、使用系统的并发容器
5 线程安全的单例模式
双重检查锁定
image.png
网友评论