正式06-题目

HashMap如何实现线程安全,ConcurrentHashMap原理
hashmap在多线程条件下容易造成死循环,cpu的利用率达到100%,
在多线程环境下保证hashmap安全主要有以下方法:
1、hashtable
2、concurrenthashmap
3、java.util.Collections.SynchronizedMap() 保证hashmap对象,得到线程安全的map,
并在此map上进行操作
4、自己加锁,当然这是严重不推荐的。
hashtable实现线程安全的原理
public synchronized V get(Object key) {  
    Entry tab[] = table;  
    …… //此处省略,具体的实现请参考 jdk实现  
}
hashtable的方法get()、put()、remove()等每个方法本身都是synchronized的,
不会出现两个线程同时对数据操作的情况,因此保证了线程的安全性,但是
也大大的降低执行效率,因此是不推荐的。
使用 java.util.Collections.synchronizedMap(Map<K,V>) 方法进行封装

 从实现源代码可以发现,其封装的本质和 Hashtable 的实现是完全一致的,即对原Map本身的方法进行加锁,加锁的对象或者为外部指定共享对象mutex,或者为包装后的线程安全的Map本身。Hashtable 可以理解为 SynchronizedMap mutex=null 时候的特殊情况。因此这种同步方式的执行效率也是很低的。

既然已经有了Hashtable, 为什么还需要Collections 提供的这种静态方法包装哪?很简单,这种包装是Java Collection Framework提供的统一接口,除了用于 HashMap 外,还可以用于其他的Map。当然 除了对Map进行封装,Collections工具类还提供了对 Collection(比如Set,List)的线程安全实现封装方法,具体请参考 java.util.Colletions 实现,其原理和 SynchronizedMap 是一致的。

(三) 使用 java.util.concurrent.ConcurrentHashMap 类。这是 HashMap 的线程安全版,同 Hashtable 相比,ConcurrentHashMap 不仅保证了访问的线程安全性,而且在效率上有较大的提高。

可以看出,相对 HashMap 和 Hashtable, ConcurrentHashMap 增加了Segment 层,每个Segment 原理上等同于一个 Hashtable, ConcurrentHashMap 为 Segment 的数组。

向 ConcurrentHashMap 中插入数据或者读取数据,首先都要讲相应的 Key 映射到对应的 Segment,因此不用锁定整个类, 只要对单个的 Segment 操作进行上锁操作就可以了。理论上如果有 n 个 Segment,那么最多可以同时支持 n 个线程的并发访问,从而大大提高了并发访问的效率。

单个 Segment 的进行数据操作的源码如下:
 
Java代码  收藏代码
V put(K key, int hash, V value, boolean onlyIfAbsent) {  
            lock();  
            try {  
                int c = count;  
                if (c++ > threshold) // ensure capacity  
                    rehash();  
  
                 …… // 代码省略,具体请查看源码                  
  
            } finally {  
                unlock();  
            }  
        }  
  
V replace(K key, int hash, V newValue) {  
            lock();  
            try {  
                HashEntry<K,V> e = getFirst(hash);  
                
                 …… // 代码省略,具体请查看源码     
                 
            } finally {  
                unlock();  
            }  
        }  
 
可见对 单个的 Segment 进行的数据更新操作都是 加锁的,从而能够保证线程的安全性。

讲到synchronized,说一下它的锁的不同粒度

synchronized粒度问题,同步方法和同步代码块的区别
同步方法把一些前置逻辑和后置逻辑都同步了,不需要同步的代码都同步了,
而同步代码块则只需要在需要同步的地方使用同步代码块 不影响方法里的其他代码。

4、synchronized和reentrantlock的区别,当都被interrupt时,有什么区别
————————————————
版权声明:本文为CSDN博主「静谧阳光」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/liuyang755855737/article/details/78305273
很多人说reentrantlock的性能要远远高于synchronized,实际上在jdk1.6之后,synchronized的性能有着很大的提高,
使得synchronized的性能和重入锁的性能差不多
下面我们来看看主要的区别:
1、重入锁的灵活性更好
重入锁需要手动加锁和释放锁,所有其灵活性更好,
2、重入锁可以提供中断处理的能力
比如一个线程在等待一把锁,在synchronized中,只有两种情况,一是获得锁,二是要么继续保持等待。
而使用重入锁就会有另外一种可能,那就是线程可以被中断,也就是在等待锁的过程中,程序可以根据需要取消对锁的请求。
这种情况对于处理死锁是有一定帮助的。
重入锁提供lockInterruptibly()方法实现可中断的锁,外界可以通过调用interrupt()来中断线程
3.锁申请等待限时
处理等待外部通知之外,要避免死锁还有另外一种方法,就是限时等待。依然以约朋友打球为例,如果朋友迟迟不来,又无法联系到他,那么在等待1-2个小时后,我想大部分人都会扫兴离去。对线程来说也是这样,通常我们无法判断为什么一个线程迟迟拿不到锁,也许是死锁了,也许是因为产生了饥饿,单如果给定一个等待时间,让线程自动放弃,那么对系统来说是有意义的。我们可以使用tryLock()方法进行一次限时的等待。tryLock()方法有两个参数,一个表示等待时长,一个表示计时单位,如果超过这个计时还没获得锁,就会返回false,如果获得锁返回true。tryLock()方法也可以不带参数直接运行,这种情况下当前线程会尝试获取锁,如果锁未被占用,则获取锁成功,返回true,如果锁被其他线程占用,则当前线程不会进行等待,立即返回false。这种模式不会引起线程等待,因此也不会产生死锁。

4.公平锁
在大多数情况下,锁的申请都是非公平的。也就是说,当线程1首先请求获取锁,接着线程2也请求获取锁,那么当锁可用时,不一定哪个线程会获取锁。因此不能保证其公平性。与之对应的则为公平锁,它会按照时间的先后顺序保证先到先得。公平锁的特点是不会产生饥饿,而synchronized是不能做到公平锁的。重入锁提供了一个构造函数public ReentrantLock(boolean fair)来控制是否是公平锁。当fair为true时表示为公平锁。不过因为公平锁需要维护一个有序队列,因此性能比较低下。reentrantlock默认采用非公平锁。

5jvm内存模型,类的加载过程













































全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务