Java - 线程安全程序

时间:2017-05-18 12:33:06

标签: java multithreading thread-safety

考虑以下代码:

public class Test
    {
        private List<Object> list;

        public Object get(int id)
        {
            return list.get(id);
        }

        public void add(Object el)
        {
            list.add(el);
        }

        public List<Object> getList()
        {
            return list;
        }
    }

我必须确保它是线程安全的(不会出现同步错误)。我对这件事情很陌生,所以我的猜测是:

  1. 我们将synchronized添加到add()get()方法,我们将synchronized(Test.class)添加到getList()方法;

  2. 我们将list设为静态且所有方法都是静态的,然后我们添加synchronized;

  3. 这就是我想出来的,虽然它们都不是正确的但是可能。

5 个答案:

答案 0 :(得分:3)

你的观点#1似乎有效。我会将synchronized放在get()add()以及getList()上,但我不会放任何静态内容,而是在调用getList()时返回列表的副本

由于您没有指定哪种类型,我以ArrayList为例编写了代码段。

  public synchronized List<Object> getList()
  {
      return new ArrayList<Object>(list);
  }

如果你想强调好的做法,你可以返回某种不可变的集合,以防止这个类的“用户”编辑列表,相信它可能会影响状态。

public synchronized List<Object> getList()
{
    return Collections.unmodifiableList(list);
}

答案 1 :(得分:2)

同步addgetgetList,另外在getList返回UnmodifiableList

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

public class Test {
    private List<Object> list = new LinkedList<>();

    public synchronized Object get(int id) {
        return list.get(id);
    }

    public synchronized void add(Object el) {
        list.add(el);
    }

    public synchronized List<Object> getList() {
        return Collections.unmodifiableList(list);
    }
}

您还需要同步getList,因为Collections.unmodifiableList在内部使用提供的列表的Iterator来复制它,因此如果不是ConcurrentModificationException,它可以提供synchronized使用add方法。

答案 2 :(得分:2)

使用静态修饰符意味着为Test类的所有实例共享相同的list字段 因此,你的班级应该是一个单身人士,而且似乎不是设计成它 因此,使用静态似乎无法满足您的需求。

为了使您的类线程安全,您必须限制其状态可能由多个线程同时更改的可能性。

所以应该改变:

    public List<Object> getList()

否则客户端可能会同时修改返回的List实例。

您应该返回一个不可修改的列表。

您还应该同步添加和删除元素的方法,以避免并发添加或删除操作。

通过这种方式,类同步:

public class Test
    {
        private List<Object> list;

        public synchronized Object get(int id)
        {
            return list.get(id);
        }

        public synchronized void add(Object el)
        {
            list.add(el);
        }

        public synchronized List<Object> getList() {
           return Collections.unmodifiableList(list);
        }

    }

现在如果你对这些方法进行链接调用,你也应该同步它们,因为在它们的调用之间,另一个线程可能会改变列表的状态。

例如这段代码:

Test test = ...;
int index = ...;
Object myObject = ...;

if (test.get(index) != myObject){
    test.add(myObject);
}

应该用这种方式写出来:

Test test = ...;
int index = ...;
Object myObject = ...;

synchronized(test){
  if (test.get(index) != myObject){
      test.add(myObject);
  }
}

答案 3 :(得分:1)

它们都不完全正确。

  

我们将synchronized添加到add()和get()方法,并将synchronized(Test.class)添加到getList()方法;

将同步添加到add()get()getList()。但是将synchronized(Test.class)添加到getList()是错误的,因为您要添加类锁而不是对象级锁来访问对象变量。

  

我们使列表静态并且所有方法都是静态的,然后我们添加synchronized;

如果您只需要Test =&gt;的所有实例中的一个列表,则必须执行此操作类级别成员变量而不是对象级别成员变量。否则,只需继续实例方法级别锁定而不是类级别锁定。遵循第一种方法。

对于类级变量,请使用类级锁。对于对象级变量,请使用对象级锁。

请参阅Synchronized MethodsIntrinsic Locks and Synchronization的oracle页面,以便更好地理解概念。

文档页面中的一些注释:

对象级锁定:

制作这些方法synchronized有两个影响:

  1. 首先,对同一对象的两个synchronized方法的调用不可能进行交错。当一个线程正在为一个对象执行synchronized方法时,所有其他线程都会为同一个对象阻塞synchronized方法(暂停执行),直到第一个线程完成该对象为止。

  2. 其次,当synchronized方法退出时,它会自动为同一对象的synchronized方法的任何后续调用建立一个before-before关系。这可以保证所有线程都可以看到对象状态的更改

  3. 班级锁定:

    您可能想知道在调用静态synchronized方法时会发生什么,因为static方法与类关联,而不是与对象关联。在这种情况下,线程获取与类关联的Class对象的内部锁。 因此,对类的静态字段的访问由一个锁定控制,该锁定与该类的任何实例的锁不同。

答案 4 :(得分:0)

那段代码已经是线程安全的。为什么 ?因为全局变量是非静态变量。所有线程都会创建它自己的变量 List<Object> list; 除非您将 List<Object> list; 设为 静态 变量。