为什么同步在以下代码中不起作用?

时间:2012-10-26 13:50:18

标签: java multithreading java-ee synchronized

此代码有时会抛出异常,即使我在synchronized运行方法块中使用了removeFirst方法synchronized,我也会在synchronizedList上添加和删除元素。< / p>

public class NameDropper extends Thread {

    private NameList n1;

    public NameDropper(List list) {
        this.n1 = new NameList(list);
    }

    public static void main(String[] args) {
        List l = Collections.synchronizedList(new LinkedList());
        NameDropper n = new NameDropper(l);
        n.n1.add("Ozymandias");
        Thread[] t = new NameDropper[10];
        for (int i = 1; i <= 10; i++) {
            t[i - 1] = new NameDropper(l);
            t[i - 1].setName("T" + Integer.toString(i - 1));
            t[i - 1].start();
        }
    }

    public void run() {
        synchronized (this) {
            try {
                Thread.sleep(50);
                String name = n1.removeFirst();
                System.out.println(Thread.currentThread().getName() + ": "
                    + name);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
     }
}

class NameList {
    private List names = null;

    public NameList(List list) {
        this.names = list;
    }

    public synchronized void add(String name) {
        names.add(name);
    }

    public synchronized String removeFirst() {
        if (names.size() > 0)
            return (String) names.remove(0);
        else
            return null;
      }
}

它抛出的例外:

T1: Ozymandias    
T2: null    
*Exception in thread "T3" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0    
    at java.util.LinkedList.entry(Unknown Source)
    at java.util.LinkedList.remove(Unknown Source)
    at java.util.Collections$SynchronizedList.remove(Unknown Source)
    at NameList.removeFirst(NameDropper.java:57)*
T0: null    
T8: null    
*at NameDropper.run(NameDropper.java:33)*      
T6: null    
T4: null    
T9: null    
T7: null    
T5: null    

5 个答案:

答案 0 :(得分:5)

您正在为每个帖子创建一个新的NameDropper实例 因此,synchronized方法实际上并不锁定,因为两个线程从不使用每个实例。

答案 1 :(得分:2)

正如其他人所指出的,你有一个竞争条件,因为所有的线程都是自己同步的。您需要一个公共对象来同步。

我建议您在列表本身上进行同步。这意味着任何争用相同列表的实例都会相互阻塞,并且不会阻止任何不会被阻止的线程。您的添加和删除方法应为:

public void add(String name) {
    synchronized (name) {
        names.add(name);
    }
}

public String removeFirst() {
    synchronized (name) {
        if (names.size() > 0)
            return (String) names.remove(0);
        else
            return null;
    }
}

答案 2 :(得分:1)

即使您使用Collections.synchronizedList,代码中也存在争用条件。

以下是您的代码中的竞赛编码示例。

lock(NameDropper[0])                            lock(NameDropper[1])
 names.size() > 0 is true                       names.size() > 0 is true  
                                                names.remove(0)
 names.remove(0) <--- Error here.

由于您为每个线程NameDropper创建which shares single instance of List实例,因此您有这种竞争条件。

您可以为每个NameDropper

创建单独的列表
        List l1 = Collections.synchronizedList(new LinkedList());
        t[i - 1] = new NameDropper(l1);

这样每个NameDropper都有自己的List实例。

答案 3 :(得分:1)

一般来说:
1)由于每次都在创建类的新实例,因此基本上没有“公共”对象可供所有线程锁定。你应该定义类似的东西:

static final Object lock = new Object();
取而代之的是

synchronize

2)恕我直言,最好实施Runnable而不是扩展Thread

答案 4 :(得分:0)

正如其他人所说,NameList未被共享。这是一种最少重新编码来修复代码的方法(还有其他方法):

更改构造函数以获取NameList(而不是List)。

public NameDropper(NameList list) {
    this.n1 = list;
}

创建您当前正在创建List的NameList。

NameList l = new NameList(Collections.synchronizedList(new LinkedList()));