初探LinkedList线程安全问题(一)

Java中LinkedList是线程不安全的,那么如果在多线程程序中有多个线程访问LinkedList的话会出现什么问题呢?

抛出ConcurrentModificationException

JDK代码里,ListItr的add(), next(), previous(), remove(), set()方法都会跑出ConcurrentModificationException。

final void checkForComodification() {
        if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    }

代码中,modCount记录了LinkedList结构被修改的次数。Iterator初始化时,expectedModCount=modCount。任何通过Iterator修改LinkedList结构的行为都会同时更新expectedModCount和modCount,使这两个值相等。通过LinkedList对象修改其结构的方法只更新modCount。所以假设有两个线程A和B。A通过Iterator遍历并修改LinkedList,而B,与此同时,通过对象修改其结构,那么Iterator的相关方法就会抛出异常。这是相对容易发现的由线程竞争造成的错误。

通过LinkedList对象修改其结构

如果两个线程都是通过LinkedList对象修改其结构,会发生什么呢?我们先看一下JDK中LinkedList的数据结构。

这是一个双向循环链表。header的作用就是能快速定位链表的头和尾。图中,“n”表示next,“p”表示previous。header的n指向first element;p指向last element。当一个线程A调用LinkedList的addFirst方法时(假设添加节点“4”),它首先更新“4”的n和p,n->3, p->header。此为第一步。第二步,更新节点“3”和herder的n和p,3n不变, 3p->4, headern->4, headerp不变。假设两个线程A,B同时调用addFirst(4), addFirst(5),会发生什么呢?很可能4,5的n指向3,p都指向header。(这里不是很确定,没有写代码测试,貌似无法从LinkedList获得entry)。也可能addFirst后,紧接着发现getFirst已经不是刚刚加入的元素。

package linkedlist;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.concurrent.Executors;

public class Test {

    private final LinkedList<Integer> list = new LinkedList<Integer>();

    public Test() {
        Thread add1 = new Thread(new Runnable() {

            @Override
            public void run() {
                int number = 0;
                while (true) {
                    if (number == 10)
                        number = 0;
                    // System.out.println("Writing " + number);
                    list.addFirst(number);
                    int first = list.getFirst();
                    if (first != number) {
                        System.err.println("ERROR!!! " + number + " " + first);
                        System.exit(-1);
                    }
                    number++;
                    System.out.flush();
                    try {
                        Thread.sleep(2);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        });

        Thread add2 = new Thread(new Runnable() {

            @Override
            public void run() {
                int number = 100;
                while (true) {
                    if (number > 119) {
                        number = 100;
                    }
                    // System.out.println("Writing " + letter);
                    // System.out.flush();
                    list.addFirst(number);
                    int first = list.getFirst();
                    if (first != number) {
                        System.err.println("ERROR!!! " + number + " " + first);
                        System.exit(-1);
                    }
                    number++;
                    try {
                        Thread.sleep(2);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        });

        Executors.newCachedThreadPool().execute(add1);
        Executors.newCachedThreadPool().execute(add2);
        // Executors.newCachedThreadPool().execute(read2);
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        Test test = new Test();
    }
}

程序输出:

ERROR!!! 0 100

总结一下,线程安全问题是由多个线程同时写或同时读写同一个资源造成的。这里只是通过分析LinkedList的相关源码浅显的分析,下一步就要考虑如何解决或避免线程竞争了。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值