1

直奔主题,我编写了一个代码来测试java中的并发性,使用同步方法:

代码:

public class ThreadTraining {

    public static class Value {

        private static int value;

        public static synchronized void Add() {
            value++;
        }

        public static synchronized void Sub() {
            value--;
        }

        public static synchronized int Get() {
            return value;
        }
    }

    public static class AddT implements Runnable {

        public static String name;

        public AddT(String n) {
            name = n;
        }

        @Override
        public void run() {
            while (Value.Get() < 100) {
                int prev = Value.Get();
                Value.Add();
                System.out.println("[" + name + "] - changed value from " + prev + " to " + Value.Get());
            }
        }
    }

    public static class SubT implements Runnable {

        public static String name;

        public SubT(String n) {
            name = n;
        }

        @Override
        public void run() {
            while (Value.Get() > (-100)) {
                int prev = Value.Get();
                Value.Sub();
                System.out.println("[" + name + "] - changed value from " + prev + " to " + Value.Get());
            }
        }
    }

    public static void main(String[] args) {
        Thread threads[] = new Thread[3];
        for (int i = 0; i < 4; i++) {
            if (i % 2 == 0) {
                threads[i] = new Thread(new AddT("Adder - Thread #" + i));
            } else {
                threads[i] = new Thread(new SubT("Subtractor - Thread #" + i));
            }
            threads[i].start();
        }
    }
}

尽管“同一对象上同步方法的两次调用不可能交错”,但这段代码的执行并不可靠。(来源:Oracle 的并发教程 - 同步方法),给出不可靠的输出,如下所示:(注意,输出中的任何非模式变化都在“...”行之间表示,而不仅仅是不可靠的行为

-----[Thread #0 - Adder] has been created!
=====[Thread #0 - Adder] has been started!
-----[Thread #1 - Subtractor] has been created!
[Thread #0 - Adder] - changed value from 0 to 1
[Thread #0 - Adder] - changed value from 1 to 2
[Thread #0 - Adder] - changed value from 2 to 3
...\*goes on, adding as expected, for some lines*\
[Thread #0 - Adder] - changed value from 83 to 84
[Thread #0 - Adder] - changed value from 84 to 85
-----[Thread #2 - Adder] has been created!
=====[Thread #1 - Subtractor] has been started!
[Thread #0 - Adder] - changed value from 85 to 86
[Thread #1 - Subtractor] - changed value from 86 to 85
[Thread #1 - Subtractor] - changed value from 86 to 85
[Thread #1 - Subtractor] - changed value from 85 to 84
...\*goes on, subtracting as expected, for some lines*\
[Thread #1 - Subtractor] - changed value from -98 to -99
[Thread #1 - Subtractor] - changed value from -99 to -100 \*This thread ends here, as it reaches the state where (value>(-100))==false*\
=====[Thread #2 - Adder] has been started!
[Thread #2 - Adder] - changed value from -100 to -99
[Thread #2 - Adder] - changed value from -99 to -98
[Thread #2 - Adder] - changed value from -98 to -97
...\*goes on as expected...*\
[Thread #2 - Adder] - changed value from -67 to -66
[Thread #2 - Adder] - changed value from -66 to -65
-----[Thread #3 - Subtractor] has been created!
=====[Thread #3 - Subtractor] has been started!
[Thread #3 - Subtractor] - changed value from -65 to -66
[Thread #3 - Subtractor] - changed value from -66 to -67
...\*goes on as expected...*\
[Thread #3 - Subtractor] - changed value from -71 to -72
[Thread #3 - Subtractor] - changed value from -72 to -73 \*NOTE: From -73 it goes to -74, without a Subtractor-action in between! WTF???*\
[Thread #2 - Adder] - changed value from -74 to -73
[Thread #2 - Adder] - changed value from -73 to -72
...\*goes on as expected...*\
[Thread #2 - Adder] - changed value from 98 to 99
[Thread #2 - Adder] - changed value from 99 to 100 \*This adder ends here, adder thread #0 probably ends after next line...but not before doing something crazy!*\
[Thread #0 - Adder] - changed value from 85 to 86 \*What the hell are these values doing here? Oh wait, next lines is...*\
[Thread #3 - Subtractor] - changed value from -73 to -47\*...Holy mother of god!*\
[Thread #3 - Subtractor] - changed value from 100 to 99
[Thread #3 - Subtractor] - changed value from 99 to 98
...
[Thread #3 - Subtractor] - changed value from -98 to -99
[Thread #3 - Subtractor] - changed value from -99 to -100 \*The logical nightmare is finally over.*\

使用同步方法不可靠吗?还是实施有误?(如果是这样,那么有什么问题?以及如何解决它?)

4

4 回答 4

3

你的实现有点偏离。'Get'、'Add' 和 'Sub' 都被锁定了,但是你的 'Get' 和你的加减之间有一个差距。刚刚执行“获取”的线程可以在该间隙中休息,其他人会更改该值。如果您希望多个方法调用都作为单个操作发生,则需要在比单个方法“更大”的东西上进行同步。

synchronized (Value.class) {
  int prev = Value.Get();
  Value.Add();
  System.out.println("[" + name + "] - changed value from " + prev + " to " + Value.Get());
}

请注意,这仍然有一个问题,当您输入时 <100 可能仍然不正确,因此您应该重新检查它。(当然,锁定 Class 对象并不是您通常想要在“真实”代码中执行的操作 :))

于 2012-05-15T17:51:10.933 回答
2

您所看到的不是“不可靠”,也不是实施问题。您正在处理大量的竞争条件。例如:

while (Value.Get() < 100) {
     // other threads could have called `Add()` or `Subtract()` here
     int prev = Value.Get();
     // other threads could have called `Add()` or `Subtract()` here
     Value.Add();
     // other threads could have called `Add()` or `Subtract()` here
     System.out.println("[" + name + "] - changed value from " + prev + " to " + Value.Get());
}

您在此循环中调用Get()了 3 次,并且不能保证其他线程在每次调用之间更改了Get(). 如果您将代码更改为如下所示,它将产生更一致的输出:

while (true) { {
     // only call get once
     int prev = Value.Get();
     if (prev >= 100) {
        break;
     }
     // return the new value from add
     int newValue = Value.Add();
     System.out.println("[" + name + "] - changed value from " + prev + " to " + newValue);
}

// here's the new version of add
public static synchronized int Add() {
    value++;
    return value;
}

Get()请注意,在和方法调用之间仍然存在竞争条件,Add()因为它是一个测试然后是一个集合。

于 2012-05-15T17:50:10.910 回答
2

要了解您的代码有什么问题,请考虑以下事项:

int prev = Value.Get();
Value.Sub();
System.out.println(... + Value.Get());

synchronized三个操作Get(), Sub(),中的每一个Get()都保证正确和原子地工作。但是,不能保证在这些操作和 modify之间不会出现另一个线程Value

每当您需要确保一系列操作的原子性时,您应该提供一个一次性synchronized执行所有操作的方法,或者使用如下外部synchronized块:

synchronized (Value.class) {
    int prev = Value.Get();
    Value.Sub();
    System.out.println(... + Value.Get());
}
于 2012-05-15T17:50:54.480 回答
1

这是完美运行的代码。它补充了@Affe 和@Aix 在这里给出的答案。

public class ThreadTraining {

    public static class Value {

        private static  int value;

        public static synchronized void Add() {
            value++;

        }

        public static synchronized void Sub() {
            value--;
        }

        public static synchronized int Get() {
            return value;
        }
    }

    public static class AddT implements Runnable {

        public static String name;

        public AddT(String n) {
            name = n;
        }

        @Override
        public void run() {

            while (Value.Get() < 100) {
                 synchronized(Value.class){
                int prev = Value.Get();
                Value.Add();
                System.out.println("[" + name + "] - changed value from " + prev + " to " + Value.Get());
            }
            }
        }
    }

    public static class SubT implements Runnable {

        public static String name;

        public SubT(String n) {
            name = n;
        }

        @Override
        public void run() {

            while (Value.Get() > (-100)) {
            synchronized(Value.class){
                int prev = Value.Get();
                Value.Sub();
                System.out.println("[" + name + "] - changed value from " + prev + " to " + Value.Get());
            }
            }
        }
    }

    public static void main(String[] args) {
        Thread threads[] = new Thread[3];
        for (int i = 0; i < 4; i++) {
            if (i % 2 == 0) {
                threads[i] = new Thread(new AddT("Adder - Thread #" + i));
            } else {
                threads[i] = new Thread(new SubT("Subtractor - Thread #" + i));
            }
            threads[i].start();
        }
    }
}

通过不按照修改后的代码所示放置同步块,您可以让线程获取变量值,而不会原子地完成操作。因此导致不一致。

于 2012-05-15T18:35:09.137 回答