交替锁实现线程安全链表
常见的线程安全链表
1、Synchronized
@Deprecated
public synchronized void insert(int value) {
throw new UnsupportedOperationException();
}
- 使用synchronized内置锁,保证插入操作是线程安全的
- 坏处:性能差
- 好处:实现简单
2、ReentrantReadWriteLock
//使用伪代码
writeLock.lock();
insert(value);
writeLock.unlock();
- 使用读写锁实现读写分离
- 好处:读写分离,提高性能
- 坏处:对于写来说,还是与内置锁性能类似
可能还有用Collecions工具类进行普通的list做线程安全封装的,但是从实质上来看,这些的性能都是差不多。
可以这么说,以上的安全实现都是锁整个链表,那么有没有其它方式呢,这就是我今天要分享的交替锁!
交替锁的实现原理
交替锁就是使用两把锁,交替释放,从而保证链表中未加锁的部分能够被其它线程自由访问,大大提高了性能。特别是当链表很长的时候,一个插入操作的时间大概假设是N,此时如果有10个线程执行插入操作,原先锁整个链表需要花费10N,而使用交替锁,最好的情况是接近于N!
以下就是交替锁的算法图示:(自上而下)
交替锁的实现源码
/**
* @author linxu
* @date 2019/8/20
* <tip>take care of yourself.everything is no in vain.</tip>
* 利用交替锁实现并发的有序链表
*/
public class ConcurrentSortedList {
private final Node head;
private final Node tail;
public ConcurrentSortedList(Node head, Node tail) {
this.head = head;
this.tail = tail;
}
public ConcurrentSortedList() {
head = new Node();
head.value=Integer.MAX_VALUE;
tail = new Node();
tail.value=Integer.MIN_VALUE;
head.next = tail;
tail.next = head;
}
/**
* 内部结点
*/
private class Node {
int value;
Node prev;
Node next;
ReentrantLock lock = new ReentrantLock(false);
public Node() {
}
public Node(int value, Node prev, Node next) {
this.next = next;
this.prev = prev;
this.value = value;
}
}
/**
* 使用交替锁实现
*
* @param value v
*/
public void insert(int value) {
//从第一个节点开始加锁
Node current = head;
current.lock.lock();
Node next = current.next;
try {
while (true) {
next.lock.lock();
try {
//插入的条件
if (next == tail || next.value < value) {
Node node = new Node(value, current, next);
next.prev = node;
current.next = node;
return;
}
} finally {
//总是先释放前一把锁
current.lock.unlock();
}
current = next;
next = current.next;
}
} finally {
//释放最后的一把锁
next.lock.unlock();
}
}
}
思考
为何在java.util中不是对链表这么加锁的,其实它有它的道理,因为java中的内置链表添加元素的添加到链表尾部的,直接获取尾部元素,然后执行tail.next=new node,因此正常情况下都是直接锁整个链表。但是在实现如以上的有序链表,这个时候是需要遍历整个链表,就可以采用交替锁,从而极大地优化了链表的插入性能。