Java中形式参数的同步化

5

假设我有一个方法,可以由两个或多个线程访问,我想使它变得线程安全。

public int getVal(int x, int y, MyClass myObj)  
{
   int z;

   z = getInt(myObj);

   return x + y + z;  
}

在这种情况下,我认为我们不必同步x + y,因为它们是原语类型。
假设`getInt(myObj)`修改了myObj的状态,并影响了z的值。
因此,我将不得不对线路`z = getInt(myObj);`提供同步,但只有当两个线程通过相同的'myObj'引用传递相同的实例时才需要同步。作为API编码者,我不知道两个线程是否会将相同的实例传递给'myObj'。在某些情况下,这些线程可能会在'myObj'引用中传递相同的MyClass实例,在其他情况下,它们可能会在'myObj'引用中传递不同的MyClass实例。
那么,如何确保行`z = getInt(myObj)`的线程安全?(当然,我们不想在传递的实例不同时进行同步,并且只需要在传递的实例相同时进行同步。可以清楚地看出这是无法确定的)。
假设无法使MyClass成为不可变的,我认为以下可能是解决方案。
synchronized(myObj)
{
  z = getInt(myObj);
}

这是一个正确的解决方案吗?除此之外,我们还可以采取哪些措施来确保线程安全性。
z = getInt(myObj); (but only in case of different instances)?

你的同步块已经足够了。当同步对象的监视器被另一个线程持有时,JVM会在该调用上阻塞。 - Brent Worden
@Brent,有没有其他的方法呢?只是好奇! - Real Red.
你的synchronized()是危险的。请查看我下面的答案。 - iluxa
6个回答

3
您的理解是正确的。当您在一个对象上进行同步时,它会锁定该实例而不是该类。因此,如果我将对象的相同*实例*传递给两个不同的方法,它将正确地锁定该对象。但是,如果我传递两个不同的实例,就不会有任何锁定,因为两个实例都有自己的

3
更好的方式是在你的MyClass类中同步getInt方法。 - Amir Raminfar
他所做的是正确的,但是不好的实践。通过在myObj上锁定,他可能会为客户端代码引入陷阱,因为该对象也想在同步时使用它。https://dev59.com/KnRB5IYBdhLWcg3w-8Po#442601 - Matt Wonlaw
没错,但如果他将工作委托给getInt()并使用内部对象进行锁定,我认为他就可以了。但是,是的,我同意这有一个陷阱。 - Amir Raminfar

2
如果`getInt`不修改`this`的状态,那么该方法是线程安全的。`myObj`对象的线程安全性由它的类`MyClass`或持有它的对象负责,而不是所有可能将其作为参数的方法,我的看法是如此。
虽然你的解决方案(`synchronized(myObj)`)是正确的:如果两个线程都使用相同的`myObj`,它们将无法同时执行`getInt`方法。如果两个`myObjs`不同,则它们将同时执行。

如果有人同时调用 setInt,那该怎么办?如果其他人可能正在写入,你仍然可能会遇到读取问题。 - Mark Peters
那不是一个getter(访问器),我会认为它从输入中提取或推导出某些内容,因此不太可能有setInt。 - Robin
“myObj”对象的线程安全性是其类的责任。Java库本质上是线程安全的吗?还是像Collections库一样,假定用户会在适当的位置添加锁定? - Karmastan
@Karmastan:是的,Java库通常是线程安全的。当一个对象不是线程安全的时,我认为它应该被封装在另一个对象中,由该对象处理同步。这个对象应该避免将对象逃逸到可能保留对该对象引用的不受控制的外部方法中。而外部方法不应该负责同步。 - JB Nizet

1
synchronized(myObj) {   z = getInt(myObj); } 

这样做可以达到你的意图,但是在参数上同步会创建许多其他问题。例如,可能有其他线程已经在该对象上进行同步(例如,该对象具有正在调用的同步方法),这样就可能陷入死锁情况。

同步应该像任何其他东西一样封装起来。最好的解决方案是将 getInt 方法添加到 MyClass 中,并在该方法中对某个私有成员进行同步。这样,没有人可以干扰您用于实现同步的内容。

E.g.:

public class MyClass {
  private Object lockObject = new Object();
  public int getInt() {
    synchronized(lockObject) {
      // do your thing
    }
  }
}

看这里:Java中避免使用synchronized(this)? 强调封装同步的重要性。


0

我不同意所有“你的同步是正确的”答案。如果用户有两个线程,其中一个已经持有对象的锁,会导致死锁。

此外,x + y + z 不是原子操作。在 CPU 级别上,它将变成

int temp = x + y;
int res = temp + z;

我告诉你更多:在32位机器上long1 + long2不是原子操作。 我认为你唯一的选择是同步整个方法。

1
他的方法中添加原始类型是可以的,因为原始类型是按值传递的。方法的原始类型形式参数不能在线程之间共享,因为它们是按值传递的。 - Matt Wonlaw

0
为了回答“那么,在哪些方面可以确保线程安全,但仅在不同实例的情况下”,可以同步整个方法或创建另一个公共对象来作为所有线程的锁,并在其上进行同步,而不是使用 myObj。

0
如果问题中唯一对myObject进行修改的是该getInt方法,那么你的同步足够了。如果还有其他的修改器,请确保它们在相同的对象上进行同步。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接