同步操作将从 Java精选/Ebooks 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
HashMap多线程会导致死循环的主要原因在于并发下的Rehash()方法会造成元素之间会形成一个循环链表。
注意的是JDK1.8及以上解决了这个问题,但是不建议在多线程下使用HashMap,因为多线程下使用HashMap还是会存在其他问题比如数据丢失。在并发环境下推荐使用ConcurrentHashMap,后续篇幅中会写相关的文章。
HashMap实现了Map接口,Map接口对键值对进行映射。
Map中不允许重复的键。Map接口有两个基本的实现,HashMap和TreeMap。TreeMap保存了对象的排列次序,而HashMap则不能。HashMap允许键和值为null。
HashMap是非synchronized的,但collection框架提供方法能保证HashMap synchronized,这样多个线程同时访问HashMap时,能保证只有一个线程更改Map。
public Object put(Object Key,Object value);
该方法用来将元素添加到map中。
假设给定一个链表,删除链表中倒数第n个节点,并且返回链表的头节点。
实例:
给定一个链表: 1->2->3->4->5,和n=2。
当删除倒数第二个节点后,链表变为1->2->3->5。
分析:表明给定的n节点是有效的。
解题思路:
1、删除的节点可以通过双指针pre和index;
2、双指针开始都是指向头节点,index先走到比头节点大n的位置;
3、两个指针同时往后遍历,当index的下一个为空时,此时pre指向的就是需要删除的那一个节点;
4、删除这个节点还需要分析后续节点是否为空;
5、如果后续不为空需要后面的节点覆盖它,若为空,就只需要设置为null。
实现代码如下:
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
if(null == head){
return null;
}
if(n <= 0){
return head;
}
ListNode pre = head;
ListNode index = head;
ListNode orghead = head;
for(int i = 1;i < n ;i++){ //让i先走n步
index = index.next;
if(null == index){
return null;
}
}
while(null != index.next){ //到n的位置,pre开始遍历
orghead = pre;
pre = pre.next;
index = index.next;
}
//删除时得考虑删除节点是否有后续节点
if(null != pre.next){
pre.val = pre.next.val; //有就往前移
pre.next = pre.next.next;
}else{
if(null == pre.next){ //没有后续,需把它设置为null值
return null;
}
orghead.next = null;
}
return head;
}
}
HashMap可以使用如下代码实现:
Map map = Collections.synchronizedMap(new HashMap());
来达到同步的效果。
具体而言,该方法会返回一个同步的Map集合,这个Map封装了底层HashMap的所有方法,使得底层的HashMap可以在多线程的环境中也能够保证安全性。
相同点:
1)实现原理相同,底层都使用数组。
2)功能相同,实现增删改查等操作的方法相似。
3)都是长度可变的数组结构,很多情况下可以互用。
不同点:
1)Vector是早期JDK版本提供,ArrayList是新版本替代Vector的。
2)Vector线程安全,ArrayList重速度轻安全,线程非安全。
长度需增长时,Vector默认增长一倍,ArrayList增长50%。
当类中要操作的引用数据类型不确定时,在JDK1.5版本前使用Object来完成扩展,JDK1.5后推荐使用泛型来完成扩展,同时保证安全性。
Java中常见线程安全的Map有Hashtable、synchronizedMap和ConcurrentHashMap。
Hashtable
使用方式,代码如下:
Map<String,Object> hashtable=new Hashtable<String,Object>();
查看源码可以看出put()、get()、containsKey()等方法都使用了synchronized关键字修饰实现同步,因此它是线程安全的。
public synchronized V put(K key, V value) {//部分源代码jdk1.8
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
@SuppressWarnings("unchecked")
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}
public synchronized boolean containsKey(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return true;
}
}
return false;
}
SynchronizedMap
使用方式,代码如下:
Map<String,Object> synchronizedMap = Collections.synchronizedMap(new Hashtable<String,Object>());
查看源码可以看出其实是加了一个对象锁,在每次操作hashmap时都需要先获取这个对象锁,且这个对象锁使用了synchronized关键字修饰,锁的性能与Hashtable相差无几。
SynchronizedMap(Map<K,V> m, Object mutex) {//部分源代码jdk1.8
this.m = m;
this.mutex = mutex;
}
public int size() {
synchronized (mutex) {return m.size();}
}
public boolean isEmpty() {
synchronized (mutex) {return m.isEmpty();}
}
public boolean containsKey(Object key) {
synchronized (mutex) {return m.containsKey(key);}
}
public boolean containsValue(Object value) {
synchronized (mutex) {return m.containsValue(value);}
}
public V get(Object key) {
synchronized (mutex) {return m.get(key);}
}
public V put(K key, V value) {
synchronized (mutex) {return m.put(key, value);}
}
public V remove(Object key) {
synchronized (mutex) {return m.remove(key);}
}
ConcurrentHashMap
使用方式,代码如下:
Map<String,Object> concurrentHashMap = new ConcurrentHashMap<String,Object>();
ConcurrentHashMap是目前使用最多的线程安全的集合,也是最推荐的一个集合。
查看源码可以发现线程安全是通过cas+synchronized+volatile来实现的,其中也可看出它的锁是分段锁,所以它的性能相对来说是比较好的,整体实现还是比较复杂的。
public V put(K key, V value) {//部分源代码jdk1.8
return putVal(key, value, false);
}
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
实现获取List集合泛型类型,实例代码如下:
public static void main(String[] args) throws Exception{
Class<?> clazz = A.class;
Field field = clazz.getField("lists");
ParameterizedType type = (ParameterizedType) field.getGenericType();
System.out.println(type.getActualTypeArguments()[0]);
}
class A {
public List<String> lists = new ArrayList<>();
}
输出结果
class java.lang.String
fail-safe(安全失败)采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
分析思路
设置两个指针first和second,两个指针同时向前走,second指针每次走两步,first指针每次走一步,直到second指针走到最后一个结点时,此时first指针所指的结点就是中间结点。
注意链表为空,链表结点个数为1和2的情况下,时间复杂度为O(n)。
// 方法:查找链表的中间结点
public Node findMidNode(Node head) {
if (head == null) {
return null;
}
Node first = head;
Node second = head;
// 每次移动时,让second结点移动两位,first结点移动一位
while (second != null && second.next != null) {
first = first.next;
second = second.next.next;
}
// 直到second结点移动到null时,此时first指针指向的位置就是中间结点的位置
return first;
}
通过上述代码可以看出,当n为偶数时,得到的中间结点是第n/2+1个结点。比如链表有6个节点时,得到的是第4个节点。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。