标签:包括 基础用法 ret 实现 vol 技术 value hash set
可重入性
可重入是指:对同一个执行线程,它在获得了锁之后,在调用其他需要同样锁的代码时,可以直接调用。
可重入是通过记录锁的持有线程和持有数量来实现的,当调用被synchronized保护的代码时,检查对象是否已被锁,如果是,再检查是否被当前线程锁定,如果是,增加持有数量,如果不是被当前线程锁定,才加入等待队列,当释放锁时,减少持有数量,当数量变为0时才释放整个锁。
内存可见性
除了保证原子操作外,synchronized还有一个重要的作用,就是保证内存可见性,在释放锁时,所有写入都会写回内存,而获得锁后,都会从内存中读最新数据。
如果只是简单地操作变量的话,可以用volatile修饰该变量,替代synchronized以减少成本。
加了volatile之后,Java会在操作对应变量时插入一个cpu指令(又叫内存栅栏),保证读写到内存最新值,而非缓存的值。
死锁
死锁就是类似这种现象,比如, 有a, b两个线程,a持有锁A,在等待锁B,而b持有锁B,在等待锁A,a,b陷入了互相等待,最后谁都执行不下去。
避免死锁的方案:
死锁检查工具:Java自带的jstack命令
同步容器
Collections类有一些方法,它们可以返回线程安全的同步容器,比如:
public static <T> Collection<T> synchronizedCollection(Collection<T> c) public static <T> List<T> synchronizedList(List<T> list) public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
它们是给所有容器方法都加上synchronized来实现安全的。当多个线程并发访问同一个容器对象时,不需要额外的同步操作,也不会出现错误的结果。
加了synchronized,所有方法调用变成了原子操作,但是也不是就绝对安全了,比如:
复合操作,比如先检查再更新
例如:
public V putIfAbsent(K key, V value){ V old = map.get(key); if(old!=null){ return old; } map.put(key, value); return null; }
假设map的每个方法都是安全的,但这个复合方法putIfAbsent是安全的吗?显然是否定的,这是一个检查然后再更新的复合操作,在多线程的情况下,可能有多个线程都执行完了检查这一步,都发现Map中没有对应的键,然后就会都调用put,而这就破坏了putIfAbsent方法期望保持的语义。
伪同步,比如同步错对象。
那给该方法加上synchronized就能实现安全吗?如下所示:
public synchronized V putIfAbsent(K key, V value){ V old = map.get(key); if(old!=null){ return old; } map.put(key, value); return null; }
答案是否定的!为什么呢?同步错对象了。putIfAbsent同步锁住的的是当前类的对象,如果该类还存在其他操作map的实例方法的话,那么它操作map时同步锁住的是map,两者是不同的对象。随意要解决这个问题应该给map加锁,如:
public V putIfAbsent(K key, V value){ synchronized(map){ V old = map.get(key); if(old!=null){ return old; } map.put(key, value); return null; } }
迭代
对于同步容器对象,虽然单个操作是安全的,但迭代并不是。遍历的同时容器如果发生了结构性变化,就会抛出ConcurrentModificationException异常,同步容器并没有解决这个问题,如果要避免这个异常,需要在遍历的时候给整个容器对象加锁
并发容器
除了以上这些注意事项,同步容器的性能也是比较低的,当并发访问量比较大的时候性能很差。所幸的是,Java中还有很多专为并发设计的容器类,比如:
标签:包括 基础用法 ret 实现 vol 技术 value hash set
原文地址:https://www.cnblogs.com/JackPn/p/9426044.html