线程安全性?
当多个线程访问某一个类,不管运行时环境采用何种调度方式,或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步
或协同,这个类都能够表现出正确的行为。那么这个类就是线程安全的类。
原子性:提供了互斥访问,同一时刻只能有一个线程来对它进行操作。
可见性:一个线程对主内存的修改可以及时的被其他线程观察到。
有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序。
LongAdder 和 AtomicLong 区别?
[https://blog.csdn.net/yao123long/article/details/63683991]
锁 互斥
synchronized : 依赖java虚拟机
不可中断的锁,适合竞争不激烈,可读性好。
Lock :jdk 通过特殊的Cpu指令,代码实现,ReentranLock
可以中断的锁,多样化同步,竞争激烈时能维持常态。
Atomic:
竞争激励的时能维持常态,比Lock性能好,只能同步一个值
synchronized 同步锁
修改代码块 :大括号括起来的代码,作用于调用的对象
修饰方法 :整个方法,作用于调用的对象
修饰静态方法 :整个静态方法,作用于所有对象
修饰一个类 : 括号括起来的部分,作用于所有对象
可见性
导致共享变量在线程间不可见的原因?
线程交叉执行
重排序结合线程交叉执行
共享变量更新后的值没有在工作内存与主存间及时更新
JMM 关于synchronized的两条规定:
线程解锁前,必须把共享变量的最新值刷新到主内存中。
线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意:加锁和解锁是
同一把锁)
volatile
对于可见性,Java提供了volatile关键字来保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
通过加入内存屏障和禁止重排序优化来实现:
对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存中去。
对volatile变量读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量。
有序性
java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,但是却会影响到多线程的
并发执行的正确性。
主要包含: volatile 、 synchrionized 、lock
先天的有序性
Happens before 原则(深入理解java虚拟机)
1)程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先发生于书写在后面的操作。
2)锁定规则 :一个unlock操作先行发生于后面对同一个锁的lock操作
3)volatile 变量规则:对一个变量的写操作先发生于后面对这个变量的读操作
4)传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出结论,操作A先行发生于操作B
5)线程启动原则:Thread对象的start()方法先行发生于次线程的每一个动作
6)线程中断规则:对线程Interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
7)线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.jion()方法结束、
Thread.isAive()的返回值手段检测到线程已经终止执行
8)对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法开始。
除了这八条规则,虚拟机就可以随意对他们进行重排序。
总结
原子性: Atomic包、CAS算法、synchronized 、lock
同一时间内只有一个线程可以对其进行访问和修改。
可见性:synchronized、 volatile
一个线程对主内存的修改可以及时的被其他线程知道。
有序性: happens - before
不可变对象
不可变对象满足的条件(参考String):
对象创建以后其状态就不能修改
对象所有的域都是final类型的
对象是正确创建的(在对象创建期间,this引用没有逸出)
将类定义成final 不可以集成
将成员都变成私有的,不可以直接访问这些成员
对变量不提供set和get方法
将所有可以变的成员生命为final,这样通过构造器初始化所有成员,进行深度拷贝
安全发布对象 (这一节的第一章没有搞清楚)
发布对象:使一个对象能够被当前范围之外的代码所使用。
对象逸出:一种错误的发布。当一个对象还没有构造完成时,就使它被其他线程所见。
安全发布对象的四种方法?
1)在静态的初始化函数中初始化一个对象引用
2)将对象的引用保存到volatile类型域或者AtomicReference 对象中
3)将对象的引用保存到某一个正确的构造对象的final类型域中
4)将对象引用保存到一个由锁保护的域中
线程封闭
把一个对象封闭到一个线程里边,只允许这个对象在这个线程里进行访问。
Ad-hoc 线程封闭:程序控制实现,最糟糕,可以忽略
堆栈封闭:局部变量,并无并发问题
TreadLocal 线程封闭:特别好的方法(本质是维护了一个Map,key是线程名称,value对应线程对象)
线程不安全的写法
线程不安全 线程安全
StringBuilder StringBuffer
SimpleDateFormat jodaDateTimeFormatter
ArrayList、HashMap、HashSet 等Collections 容器均是线程不安全的
同步容器
ArrayList -> Vector (矢量)、Stack(栈)
HashMap -> HashTable(key,value 不能为空)
Collections.synchronizedXXX(List、Set、Map) //集合 排序 处理
同步容器场景越来越少
1)synchronized 会降低性能
2)并不能完全保证线程安全
并发容器 (J U C)
ArrayList -> CopyOnWriteArrayList
原理: copy一个副本,进行写操作,写操作加锁。
缺点
1、需要copy数组,就要消耗内存,如果元素比价多的时候,可能导致 gc
2、不能应用于实时读的场景
适合:读多 写少的操作
设计思想:
1、读写分离
2、最终一致性
3、使用时另外开辟控件
4、读不加锁 写是加锁的(防止多个线程copy出多个副本)
HashSet、TreeSet-> CopyOnWriteArraySet (CopyOnWriteArrayList)、ConcurrentSkipListSet(jdk1.6)
CopyOnWriteArraySet:
线程安全
1、copy开销较大
2、不支持remove操作
3、遍历速度非常快,适合读操作大于写操作的情况
ConcurrentSkipListSet:
支持自然排序、定义比较细
1、Map
2、add 、put 、move 线程安全的
3、不允许使用空元素Null
4、addAll RoveAll 等是批量线程不安全的
HashMap、 TreeMap -> ConcurrentHashMap 、ConcurrentSkipListMap
ConcurrentHashMap:
1、不允许空值
2、具有特别高的并发性
ConcurrentSkipListMap:
1、SkipList 跳表
2、key 是有序 支持更高的并发
安全共享对象策略
1)线程限制:一个被线程限制的对象,由线程独占,并且只能被占有他的线程修改
2)共享只读:一个共享只读的对象,在没有额外同步的情况下,可以被多个线程并发访问,但是任何
线程都不能修改它
3)线程安全对象:一个线程安全的对象或容器,在内部通过同步机制来保证线程安全,所以其他的线程无需额外的同步
就可以通过公共接口随意访问它
4)被守护对象:被守护对象只能通过获取特定的锁来访问
并发测试工具有哪一些?
PostMan : Http请求模拟工具
Apache Bench (AB) :Apache附带的工具,测试网站性能 (小巧灵活、命令行操作)
JMeter : Apache组织开发的压力测试工具 (图形界面话操作)
(这个仔细的去学习一下)
补充知识点
注意,为了获得较好的执行性能,Java内存模型并没有限制执行引擎使用处理器的寄存器或者高速缓存来提升指令执行速度,也没有限制编译器对指令进行重排序。
也就是说,在java内存模型中,也会存在缓存一致性问题和指令重排序的问题。
网友评论