问题描述
我正在尝试学习并发
我在玩下面的代码:
class LRUCache {
/**
Algorithm :
1. Everytime when we add the node check if it exists .
1.1 if exists then update the value .
1.2 move this node to head.
1.3 if the capaicity reaches then remove node from tail .
1.4 move the current node to head.
2. Everytime when we get
2.1 then we need to move this node to head.
3. Remove the node from tail thats it.
}
**/
private int capacity;
private AtomicInteger curSize = new AtomicInteger() ;
private ConcurrentHashMap<Integer,Node> map = new ConcurrentHashMap<>();
private Node head = new Node();
private Node tail = new Node();
private reentrantreadwritelock readWriteLock = new reentrantreadwritelock();
public LRUCache(int capacity) {
this.capacity = capacity;
head.next = tail;
tail.prev = head;
}
public int get(int key) {
if(!map.containsKey(key))
return -1;
Node node = map.get(key);
// move this current Node to front
movetoHead(node);
return node.val;
}
public void put(int key,int value) {
// The key is already present
Node curNode = map.get(key);
if(curNode != null){
// if value exist update
curNode.val=value;
// moveto head,so Now its used recently
movetoHead(curNode);
return;
}
Node newNode = new Node(value,key);
map.put(key,newNode);
addToHead(newNode);
if( curSize.incrementAndGet() > capacity){
Node nodetoRemove = tail.prev;
removeNode(nodetoRemove);
map.remove(nodetoRemove.key);
curSize.decrementAndGet();
}
}
private void movetoHead(Node node){
// remove
removeNode(node);
addToHead(node);
}
private void removeNode(Node node){
try{
readWriteLock.writeLock().lock();
Node prev = node.prev;
Node next = node.next;
prev.next = next;
next.prev = prev;
}finally{
readWriteLock.writeLock().unlock();
}
}
private void addToHead(Node node){
try{
readWriteLock.writeLock().lock();
node.next = head.next;
node.prev = head;
head.next.prev = node;
head.next = node;
}finally{
readWriteLock.writeLock().unlock();
}
}
}
class Node {
Integer val;
Integer key;
Node next ;
Node prev;
public Node(int val,int key){
this.val = val;
this.key = key;
}
public Node(){
}
}
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache obj = new LRUCache(capacity);
* int param_1 = obj.get(key);
* obj.put(key,value);
*/
public int get(int key) {
readWriteLock.readLock().lock();
try{
if(!map.containsKey(key))
return -1;
Node node = map.get(key);
// move this current Node to front
movetoHead(node);
return node.val;
}finally{
readWriteLock.readLock().unlock();
}
}
我认为这可以确保在 reads
进行时不会发生 writes
。
但是在修改 get 函数后,我在 Leetcode 中遇到 Time Limit Exceeded。谁能解释一下原因?
解决方法
我正在尝试学习并发
我在玩下面的代码:
我强烈建议你不要从这段代码中学习并发,因为它充满了各种并发错误。
来自像这样的幼稚错误:
if(!map.containsKey(key))
return -1;
Node node = map.get(key);
// move this current Node to front
moveToHead(node);
return node.val;
在 containsKey
之后和 get
之前,另一个线程可能会删除密钥,因此,我们会得到 node==null
和 NullPointerExcetion
。
更多类似这样的 Java 特定错误:
curNode.val=value;
这里的新值在没有适当同步的情况下被写入 val
,这会造成所谓的数据竞争(即在其他线程中读取 val
将返回奇怪的结果)。
修复代码的最简单方法是将每个公共方法设为 synchronized
并丢弃 readWriteLock
。
同样在这种情况下,将 AtomicInteger
和 ConcurrentHashMap
替换为 int
和 HashMap
是合理的(您不需要 synchronized
的并发版本)。
如果你想在java中学习并发,那么我会推荐:
- Java Concurrency in Practice — 这是一本 Java 作者的书,给出了在 Java 中编写并发代码的简单实用规则。 (不幸的是,恕我直言,它不够深入)
- 学习Java内存模型,它是一个part of Java Language Specification,它定义了当多个Java线程访问内存中的相同数据时会发生什么。
至少学习发生在规则之前是至关重要的。
不幸的是,我不知道任何关于此的简单明了的书/文章。 -
The Art of Multiprocessor Programming 了解各种无锁和无等待算法(确实存在诸如
AtomicInteger
之类的算法)。
一个死锁被编码:
读锁在get
中获取,然后最终调用moveToHead
;在 removeNode
内部,由 moveToHead
调用,代码尝试获取 write 锁,但由于 read
锁已被锁定,因此被阻止!
参见documentation:“写入者可以获得读锁,但反之则不然”
get
也在改变列表,所以 write 锁更合适
因为没有实现纯读操作,所以不需要ReadWriteLock
,一个普通就够了