Java 7同步方法似乎允许多个线程访问

3
我在Java 7应用程序上实现Synchronized接口时遇到了问题。我认为问题可能在于我使用静态final数组实例化多个对象,该数组在同步方法中被写入。似乎线程没有识别监视器?我能做我想做的事情吗?还是需要一种具有数组和writeIndex增量器的缓冲对象?(如果有帮助,则代码位于https://github.com/dschaper/CS112_Dan_Schaper/tree/master/Week14/Labs,这不是作业,只是一个非分级实验室。)
public class SingleDice extends Dice implements Runnable {
    String threadName;
    private static final int[] valuesArray = new int[9];
    private static int writeIndex = 0;

    public SingleDice(String tName) {
        super(1);
        threadName = tName;
    }

    public synchronized void add(int value) {
        int pos = writeIndex;
        System.out.printf("%s starting sync add(%d), writeIndex = %d\n",
            Thread.currentThread().getName(), value,
            writeIndex);
        valuesArray[pos] = value;
        System.out.printf("%s wrote %d to index %d\n",        
            Thread.currentThread().getName(), value, writeIndex);
        ++writeIndex;
        System.out.printf("Next writeIndex %d\n", writeIndex);
    }
    @Override
    public void run() {
    for (int i = 0; i < 3; i++) {
        add(super.Throw());
        }
    }
}

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SyncDiceExecutor {

public static void main(String[] args) {
    System.out.println("Creating newCachedThreadPool");
    SingleDice SD1 = new SingleDice("SD1");
    SingleDice SD2 = new SingleDice("SD2");
    SingleDice SD3 = new SingleDice("SD3");
    ExecutorService executor = Executors.newCachedThreadPool();
    executor.execute(SD1);
    System.out.printf("Starting Thread SD1 %s\n", executor.toString());
    executor.execute(SD2);
    System.out.printf("Starting Thread SD2 %s\n", executor.toString());
    executor.execute(SD3);
    System.out.printf("Starting Thread SD3 %s\n", executor.toString());
    executor.shutdown();
    System.out.printf("Execeutor shutdown called: %s\n", executor.toString());

    }
}

输出

Creating newCachedThreadPool
Starting Thread SD1 java.util.concurrent.ThreadPoolExecutor@544ec1[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]
pool-1-thread-1 starting sync add(4), writeIndex = 0
pool-1-thread-2 starting sync add(4), writeIndex = 0
Starting Thread SD2 java.util.concurrent.ThreadPoolExecutor@544ec1[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 0]
pool-1-thread-2 wrote 4 to index 0
pool-1-thread-1 wrote 4 to index 0
pool-1-thread-3 starting sync add(4), writeIndex = 1
Next writeIndex 1
pool-1-thread-2 starting sync add(6), writeIndex = 2
Starting Thread SD3 java.util.concurrent.ThreadPoolExecutor@544ec1[Running, pool size = 3, active threads = 3, queued tasks = 0, completed tasks = 0]
pool-1-thread-2 wrote 6 to index 2
Next writeIndex 3
pool-1-thread-3 wrote 4 to index 2
Next writeIndex 4
Next writeIndex 2
pool-1-thread-3 starting sync add(1), writeIndex = 4
pool-1-thread-2 starting sync add(6), writeIndex = 3
Execeutor shutdown called: java.util.concurrent.ThreadPoolExecutor@544ec1[Shutting down, pool size = 3, active threads = 3, queued tasks = 0, completed tasks = 0]
pool-1-thread-2 wrote 6 to index 4
Next writeIndex 5
pool-1-thread-3 wrote 1 to index 4
Next writeIndex 6
pool-1-thread-1 starting sync add(3), writeIndex = 4
pool-1-thread-3 starting sync add(1), writeIndex = 6
pool-1-thread-1 wrote 3 to index 6
pool-1-thread-3 wrote 1 to index 6
Next writeIndex 8
Next writeIndex 7
pool-1-thread-1 starting sync add(1), writeIndex = 8
pool-1-thread-1 wrote 1 to index 8
Next writeIndex 9

Process finished with exit code 0
2个回答

5

SD1、SD2和SD3是不同的对象,即它们不共享同一个监视器。每个对象都有自己的监视器。如果您想要在所有实例之间共享监视器,则可以使用synchronized(SingleDice.class) {...}。


谢谢帮忙!因此,方法明确为 public synchronized void add(int value) { synchronized(this) ... },就像我在上面编码的那样?(正如 @hyde 指出的那样)我只需要显式地将 (this) 更改为 (SingleDice.class)。 - SonOfAMotherlessGoat
synchronized放在方法签名中,本质上与在方法内部使用synchronized(this) {method body}是相同的。因此,您可以从签名中删除synchronized,并在方法体周围添加synchronized(SingleDice.class) { ... } - fpw
公共同步 void add(int value) {     synchronized (SingleDice.class){         int pos = writeIndex;         valuesArray[pos] = value;         System.out.printf("%s 写入 %d 到索引 %d\n", (), value, writeIndex);         ++writeIndex;     } } - SonOfAMotherlessGoat
是的,现在您可以从方法签名中删除synchronized关键字,以避免餐桌哲学家问题。 - fpw
1
哦!灯泡似乎在试图闪烁!我不需要同步整个方法,只需要原子代码。谢谢! - SonOfAMotherlessGoat

1
非静态方法上的synchronized锁定this,因此如果您有不同的对象,则它们不是互斥的。如果无法使用静态方法(该方法在类上同步),则解决方案是添加静态锁对象并对其进行同步以保护静态数据。
static final Object valueArrayLock = new Object();

...

// inside a method
synchronized(valueArrayLock) {
    // operate on valueArray
}

请注意,如果您需要在静态方法中进行锁定,请使用相同的静态锁对象,而不是使静态方法同步(或参见有关使用类对象进行锁定的其他答案)。

这个对象不是必需的,你可以使用ClassName.class。这是每个类中的静态对象。甚至可以与同步静态方法兼容,因为它正是它们所做的。 - fpw
1
@fpw 我更喜欢明确、描述性命名的锁对象,因为它更清晰。如果需要添加另一个锁对象,这样做也更容易/更清洁。但这只是风格问题。 - hyde
当然,我只是想指出这会破坏与同步静态方法的兼容性——如果你混合使用两种风格,很容易遇到餐桌哲学家问题。 - fpw
@fpw 啊,没错,说得好。我添加了一个关于在静态方法中需要使用相同锁对象的段落。 - hyde

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