有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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

这段代码有时会引发异常,即使我在run方法的synchronized块中使用了synchronized方法removeFirst,我还是在synchronizedList上添加和删除元素

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) 个答案

  1. # 1 楼答案

    正如其他人所说,名单没有被分享。以下是一种只需最少重新编码即可修复代码的方法(还有其他方法):

    更改构造函数以获取名称列表(而不是列表)

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

    在当前创建列表的位置创建名称列表

    NameList l = new NameList(Collections.synchronizedList(new LinkedList()));
    
  2. # 2 楼答案

    即使您正在使用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.
    

    因为您正在为每个线程which shares single instance of List创建NameDropper实例,所以您有这个竞争条件

    您可以做的是为每个NameDropper创建单独的列表

            List l1 = Collections.synchronizedList(new LinkedList());
            t[i - 1] = new NameDropper(l1);
    

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

  3. # 3 楼答案

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

    我建议您在列表上同步。这将意味着争夺同一列表的任何实例都会被彼此阻止,而任何未被阻止的线程都不会被阻止。添加和删除方法应为:

    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;
        }
    }
    
  4. # 4 楼答案

    您正在为每个线程创建一个新的NameDropper实例
    因此,synchronized方法实际上并没有锁定,因为每个实例从来没有被两个线程使用过

  5. # 5 楼答案

    一般来说:
    1) 由于每次都要创建一个新的类实例,所以基本上没有“公共”对象供所有线程锁定。你应该定义如下:

    static final Object lock = new Object();
    

    synchronize在这个对象上

    2)IMHO最好实现Runnable,而不是扩展Thread