2012-05-15 1 views
1

Переход прямо в точку, я сделал код для проверки параллелизма в Java, используя синхронизированные методы:В 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's concurrency tutorial - Synchronized methods), что дает результаты, которые являются ненадежными, например, следующее: (примечание, любое не patternful изменения на выходе обозначается между «...» линией, не только ненадежно поведением)

-----[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.*\ 

Является ли использование синхронных методов ненадежными? Или реализация неправильная? (Если да, то что не так?)

+0

FYI - Соглашение об именах Java: имена методов начинаются в нижнем регистре (Value.add, Value.sub, Value.get и т. Д.). – assylias

ответ

3

Ваша реализация немного выключена. «Get», «Add» и «Sub» заблокированы, но есть разрыв между вашим «Get» и добавлением или вычитанием. Поток, который только что сделал «Get», может сделать перерыв во время этого разрыва, а кто-то другой изменит значение. Если вы хотите, чтобы все вызовы с разными вызовами выполнялись как одна операция, вам нужно синхронизировать с чем-то «большим», чем отдельный метод.

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

Примечание это все еще есть проблема, где < 100 не может быть все еще актуально, когда вы входите, так что вы должны перепроверить его.(и, конечно, блокировка объекта класса - это не то, что вы обычно хотели бы сделать в «реальном» коде :))

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() метода, поскольку это испытание, а затем набор ,

2

Чтобы понять, что случилось с вашим кодом, необходимо учитывать следующее:

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

Каждый из трех synchronized операций Get(), Sub(), Get() гарантируется правильно и атомарно работать. Однако нет никакой гарантии, что другой поток не будет находиться в между этими операциями и изменить Value.

Всякий раз, когда вам нужно обеспечить атомарность в последовательности операций, вы должны либо обеспечить единый synchronized метод, который выполняет их все сразу, или использовать внешний synchronized блок, как так:

synchronized (Value.class) { 
    int prev = Value.Get(); 
    Value.Sub(); 
    System.out.println(... + Value.Get()); 
} 
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(); 
     } 
    } 
} 

По не прикладывая синхронизированный блок, как показано в модифицированном коде, выпускающие нити захватить значение переменной без операции завершается атомарно. Отсюда вытекает несогласованность.