Java并发编程基础—线程安全
《Java Concurrency In Practice》的作者 Brian Goetz 对线程安全是这样理解的,当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行问题,也不需要进行额外的同步,而调用这个对象的行为都可以获得正确的结果,那这个对象便是线程安全的。
3种典型的线程安全问题
1)运行结果错误;
2)发布和初始化导致线程安全问题;
3)活跃性问题。
- 死锁,死锁是指两个线程之间相互等待对方资源,但同时又互不相让,都想自己先执行
- 活锁,活锁是指正在运行的线程并没有阻塞,它始终在运行中,却一直得不到结果。
- 饥饿,饥饿是指线程需要某些资源时始终得不到,尤其是CPU 资源,就会导致线程一直不能运行而产生的问题。Java 中优先级分为 1 到 10,1 最低,10 最高。
需要注意线程安全的场景
1)访问共享变量或资源
2)依赖时序的操作
3)不同数据之间存在绑定关系
4)对方没有声明自己是线程安全的
多线程带来的性能问题
1)上下文切换
上下文切换带来的开销是比较大的,假设我们的任务内容非常短,比如只进行简单的计算,那么就有可能发生我们上下文切换带来的性能开销比执行线程本身内容带来的开销还要大的情况。
2)缓存失效
一旦进行了线程调度,切换到其他线程,CPU就会去执行不同的代码,原有的缓存就很可能失效了,需要重新缓存新的数据,这也会造成一定的开销。
3)协作开销
线程之间如果有共享数据,为了避免数据错乱,为了保证线程安全,就有可能禁止编译器和 CPU 对其进行重排序等优化,也可能出于同步的目的,反复把线程工作内存的数据 flush 到主存中,然后再从主内存 refresh 到其他线程的工作内存中,等等。因为线程安全的优先级要比性能优先级更高,这也间接降低了我们的性能。