举例来说明锁的可重入性
public class UnReentrant{ Lock lock = new Lock(); public void outer(){ lock.lock(); inner(); lock.unlock(); } public void inner(){ lock.lock(); //do something lock.unlock(); } }
outer 中调用了inner,outer 先锁住了lock,这样inner 就不能再获取lock。其实调用outer 的线程已经获取了lock 锁,但是不能在inner 中重复利用已经获取的锁资源,这种锁即称之为不可重入可重入就意味着:线程可以进入任何一个它已经拥有的锁所同步着的代码块。
synchronized、ReentrantLock 都是可重入的锁, 可重入锁相对来说简化了并发编程的开发。
如果其他方法没有synchronized 的话,其他线程是可以进入的。
所以要开放一个线程安全的对象时, 得保证每个方法都是线程安全的。
SynchronizedMap 一次锁住整张表来保证线程安全,所以每次只能有一个线程来访为map。
ConcurrentHashMap 使用分段锁来保证在多线程下的性能。
ConcurrentHashMap 中则是一次锁住一个桶。ConcurrentHashMap 默认将hash 表分为16 个桶,诸如get,put,remove 等常用操作只锁当前需要用到的桶。
这样,原来只能一个线程进入,现在却能同时有16 个写线程执行,并发性能的提升是显而易见的。
另外ConcurrentHashMap 使用了一种不同的迭代方式。在这种迭代方式中, 当iterator 被创建后集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是在改变时new 新的数据从而不影响原有的数据,iterator 完成后再将头指针替换为新的数据,这样iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变。
CopyOnWriteArrayList(免锁容器)的好处之一是当多个迭代器同时遍历和修改这个列表时,不会抛出ConcurrentModificationException。在CopyOnWriteArrayList 中,写入将导致创建整个底层数组的副本,而源数组将保留在原地,使得复制的数组在被修改时, 读取操作可以安全地执行。
1、由于写操作的时候,需要拷贝数组,会消耗内存,如果原数组的内容比较多的情况下, 可能导致young gc 或者full gc;
2、不能用于实时读的场景,像拷贝数组、新增元素都需要时间,所以调用一个set操作后,读取到数据可能还是旧的,虽然CopyOnWriteArrayList 能做到最终一致性,但是还是没法满足实时性要求;
CopyOnWriteArrayList 透露的思想
1、读写分离,读和写分开
2、最终一致性
3、使用另外开辟空间的思路,来解决并发冲突
线程安全是编程中的术语,指某个函数、函数库在多线程环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。
Servlet 不是线程安全的,servlet 是单实例多线程的,当多个线程同时访问同一个方法,是不能保证共享变量的线程安全性的。
Struts2 的action 是多实例多线程的,是线程安全的,每个请求过来都会new 一个新的action 分配给这个请求, 请求完成后销毁。
SpringMVC 的Controller 是线程安全的吗? 不是的,和Servlet 类似的处理流程。
Struts2 好处是不用考虑线程安全问题; Servlet 和SpringMVC 需要考虑线程安全问题,但是性能可以提升不用处理太多的gc,可以使用ThreadLocal 来处理多线程的问题。
volatile 保证内存可见性和禁止指令重排。
volatile 用于多线程环境下的单次操作(单次读或者单次写)。
在执行程序时,为了提供性能,处理器和编译器常常会对指令进行重排序,但是不能随意重排序,不是你想怎么排序就怎么排序, 它需要满足以下两个条件:
在单线程环境下不能改变程序运行的结果;
存在数据依赖关系的不允许重排序
需要注意的是:重排序不会影响单线程环境的执行结果,但是会破坏多线程的执
行语义。