在同一个锁上同步

时间:2016-09-26 15:35:42

标签: java multithreading synchronization

我正在读书的摘录 -

//在错误的锁上同步,因此@NotThreadSafe

public class ListHelper<E> {

    public List<E> list =  Collections.synchronizedList(new ArrayList<E>());

    public synchronized boolean putIfAbsent(E x){
        boolean absent = !list.contains(x);
        if(absent)
            list.add(x);
        return absent;
    }
}

//同步/正确锁定同步,因此@ThreadSafe
//实现客户端锁定的假设。

public class ListHelper<E> {

    public List<E> list =  Collections.synchronizedList(new ArrayList<E>());

    public  boolean putIfAbsent(E x){
        synchronized(list){
            boolean absent = !list.contains(x);
            if(absent)
                list.add(x);
            return absent;
        }
    }
}

迅速开始,作者转向第三种情况 -
//使用合成实现put-if-absent。

public class ImprovedList<E> implements List<E> {

    private final List<E> list;

    public ImprovedList(List<E>  list){
        this.list = list;
    }

    public synchronized boolean putIfAbsent(E x){

        boolean contains = list.contains(x);
        if(contains)
            list.add(x);
        return !contains;

    }
}

上述课程@ThreadSafe如何,即使list中的public final List<E> list;可能not be @ThreadSafe

4 个答案:

答案 0 :(得分:1)

我相信这个例子来自JCIP。鉴于此,

在案例3中 - 它仍然是线程安全的,因为您通过var lats = ["51.5", "21.43", "30", "40.10"]; var lons = ["-0.06", "-100.36", "-223.23", "-333.94"]; 获取ImprovedList对象上的锁定。这里需要注意的关键是即使是使用public synchronized boolean putIfAbsent(E x)关键字的此类使用list的所有其他方法也是如此。因此,在任何给定时间,只有一个线程能够使用此synchronized对象。

如果我们尝试添加其他与列表相关的方法并查看其工作原理,那就更清楚了。

list

在案例1中 - 虽然它是public class ImprovedList<E> implements List<E> { public final List<E> list; public ImprovedList(List<E> list){ this.list = list; } public synchronized boolean putIfAbsent(E x){ boolean contains = list.contains(x); if(contains) list.add(x); return !contains; } public synchronized void add(E x){ list.add(x); } //...Other list methods, but using synchronized keyword as above method. } 但它不是线程安全的,因为这里要注意的关键事项 public synchronized boolean putIfAbsent(E x)类中没有任何synchronized关键字的其他方法仍然可以更新ListHelper 。 **这些方法正在这样做,因为他们认为因为list **而安全。

如果我们尝试添加其他与列表相关的方法并查看它失败的原因,那就更清楚了。

Collections.synchronizedList(new ArrayList<E>());

所以基本上总结是使用与列表相同的锁(case-2)或使用显式锁(case -3)。

关于@Markspace问题:

  

该列表是公开的,不是线程安全的。这是什么书?

这是真的,并且在这本书中已经添加了警告。它说,

  

与Collections.synchronizedList和其他集合类似   包装器,ImprovedList假定一旦将列表传递给其构造函数,客户端将不会再次直接使用基础列表,只能通过ImprovedList访问它。

答案 1 :(得分:1)

  

你能告诉我为什么在第二种情况下,即使列表本身已经同步,也会有同步(列表){...}。

如果你完全用其他线程安全的对象创建一个O对象,那并不一定意味着O将是线程安全的。

如果您取出synchronized语句,然后让两个线程同时使用相同的listHelper.putIfAbsent(x)调用x方法,该怎么办?两个线程可以同时调用list.contains(x),两者都可以将absent设置为true。一旦他们到达那一点,他们将调用list.add(x),并且线程安全列表将包含对同一对象的两个引用。

这似乎不是你想要的。

list的线程安全意味着当两个线程同时调用list.add(x)时,它们不会对列表的内部结构造成任何损害。这意味着list将履行其承诺,即使它被多个线程同时使用。

list.contains(x)方法符合其承诺:它同时返回false因为x不在列表中。 list.add(x)实现了它的承诺,它将x引用放入列表中,就像它被告知要做的那样。

问题(如果您取消同步)不在list对象中的任何位置,因为列表中只包含一个x引用的要求是您的要求。

如果不希望列表包含多个x引用,那么必须编写阻止它的代码发生。这是synchronized语句的作用。它使得原子操作无法测试x是否在列表中然后对其做了些什么。 &#34;原子&#34;基本上意味着,两个线程不能同时进行。

  

第二种情况下的类是JCIP中给出的线程安全

短语&#34;线程安全&#34;没有确切的含义。我没有说ListHelper 线程安全;我说调用它&#34;线程安全是错误的。&#34;

我只是采取比布洛赫先生更保守的立场,这就是全部。

但是通过成为list成员public,他也很容易以非线程安全的方式使用他的类。

我为了生活而编写软件,在我的工作中,既不是必要的,也不足以让我使程序工作。我的工作是制作让客户开心的计划。如果使用我的程序有错误的方式,如果我以错误的方式使用比正确的方式看起来更有吸引力,那么客户就不会高兴,我的老板也不会高兴,并且借口,&#34;是的,但它有效&#34;不会让我摆脱困境。

答案 2 :(得分:1)

简短回答。你需要阅读另一本书。这个令人困惑和不正确。

  

//在错误的锁上同步,因此@NotThreadSafe

我假设这本书说错误对象synchronizing(你没有在“锁定”上同步)的原因是因为listpublic领域。这肯定是一个非常糟糕的做法。 public字段总是危险的 - 特别是对于多线程应用程序。

如果是“错误的锁定”,因为它锁定了helper而不是list,那么这本书是不正确的。您可以说锁定list是一种更好的做法,但如果是,则应该final

  

//同步/正确锁定,因此@ThreadSafe

     

//实现客户端锁定时不存在。

但该字段仍为public,这意味着其他一些来电者可能会在list来电putIfAbsent(...)时进行操作。除非你能以某种方式保证其他人也这样做,否则在列表而不是助手上进行同步几乎没有什么区别。这是一个非常危险的假设。如果为true,则不需要Collections.synchronizedList(...)包装器。

例如,在listHelper.list.add(x)方法完成list.contains(x)调用后,另一个来电可能会调用putIfAbsent(...) ,这意味着x将在列表中两次。不好。

  

//使用合成实现put-if-absent。

这不是thread-safe,因为List已传入,并且无法保证调用者不会继续不正确地操作列表。您可以通过将传入列表中的所有条目复制到内部列表中来使此线程安全。该列表必须由助手拥有,以使其完全是线程安全的。

  

上面的类是什么@ThreadSafe,即使列表在公共最终列表中也是如此;可能不是@ThreadSafe

不是。这3个示例都不是线程安全的。

public class ListHelper<E> {
    private final List<E> list = new ArrayList<E>();
    public boolean putIfAbsent(E x) {
        synchronized (list) {
            boolean absent = !list.contains(x);
            if (absent) {
                list.add(x);
            }
            return absent;
        }
    }
}

此类 是线程安全的,因为它拥有listlist字段不是public,而list字段上的两个操作synchronized 1}}是synchronized。如果putIfAbsent(...)关键字位于list方法而不是test admin,那么它也是线程安全的。

答案 3 :(得分:0)

我也在一本书中看到了这一点,请参阅下面的代码

public class ListHelper {

 public List<String> list =  Collections.synchronizedList(new ArrayList<String>());

    public synchronized boolean putIfAbsent(String x) throws InterruptedException{// lock is ListHelper
        boolean absent = !list.contains(x);
        Thread.sleep(1000);
        if(absent)
            list.add(x);
        return absent;
    }

   public void addList(String str) throws InterruptedException{
       Thread.sleep(500);
       list.add(str); // lock is SynchronizedCollection mutex 
   }

public static void main(String[] args) throws InterruptedException {
    ListHelper lh = new ListHelper();
    MyThread t1 = new MyThread(lh);
    MyThread2 t2 = new MyThread2(lh);
    t1.start();
    t2.start();
    Thread.sleep(1500);
    System.out.println("size="+lh.list.size());
    for(String str : lh.list){
        System.out.println("item="+str);
    }

}

}

class MyThread extends Thread{
ListHelper lh;
public MyThread(ListHelper lh){
    this.lh=lh;
}

@Override
public void run() {
    try {
        lh.putIfAbsent("maksim");
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

}

class MyThread2 extends Thread{
ListHelper lh;
public MyThread2(ListHelper lh){
    this.lh=lh;
}
@Override
public void run() {
    try {
        lh.addList("maksim");
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

}

}