2014-12-30 3 views
2

У меня есть следующий код:Java синхронизируются блок неожиданное поведение

public class Experimenter implements Runnable 
{ 
    private volatile Integer a = new Integer(0); 

    public Experimenter() throws Exception 
    { 
     System.out.println("start"); 
    } 

    public void funk() throws InterruptedException 
    { 
     synchronized (a) 
     { 
      System.out.println("in"); 
      Thread.sleep(5000); 
      System.out.println("out"); 
     } 
    } 

    public static void main(String[] args) throws Exception 
    { 
     Thread a = new Thread(new Experimenter(), "a"); 
     Thread b = new Thread(new Experimenter(), "b"); 
     Thread c = new Thread(new Experimenter(), "c"); 
     Thread d = new Thread(new Experimenter(), "d"); 
     Thread e = new Thread(new Experimenter(), "e"); 

     a.start(); 
     b.start(); 
     c.start(); 
     d.start(); 
     e.start(); 
    } 

    @Override 
    public void run() 
    { 
     try 
     { 
      funk(); 
     } 
     catch (InterruptedException e) 
     { 
      e.printStackTrace(); 
     } 
    } 
} 

я ожидал, так как только один поток может использовать синхронизированный блок, в то время, было бы напечатать что-то вроде следующего, с 5-секундным отставанием между каждым входом и выходом:

start 
start 
start 
start 
start 
in 
out 
in 
out 
in 
out 
in 
out 
in 
out 

Но вместо этого я получаю следующее. Все входы, через 5 секунд, все выходы:

start 
start 
start 
start 
start 
in 
in 
in 
in 
in 
out 
out 
out 
out 
out 

Не могли бы помочь объяснить это?

ответ

3

Довольно просто: ваш a не используется ни одним из ваших Experimenter s; это переменная экземпляра, по одному на Experimenter. И volatile практически бесполезен в этой ситуации.

Если вы хотите общий замок, сделайте его переменной private static final. И нет необходимости в volatile в этой ситуации!

Но я бы пошел с решением @ JBNizet, которое намного чище.

EDIT: почему final? Потому что он никогда не будет меняться после инициализации; но самый важный аспект переменной final исходит из модели памяти Java, в которой указано, что инициализация переменной finalпроисходит до при чтении этой переменной. Это очень мощное правило.

+0

Не могли бы вы объяснить необходимость «финала»? – Hele

+0

См. Сообщение редактирование, это прояснит ситуацию. – fge

+0

, тогда он должен использовать блокировку уровня класса? – Prashant

2

Каждый поток синхронизируется самостоятельно, так как каждый поток использует свой собственный экземпляр Experimenter, и каждый экземпляр Experimenter имеет свой собственный экземпляр Integer, служащий блокировкой. Если вы хотите, чтобы потоки синхронизировались, вам нужно предоставить уникальную блокировку между всеми экземплярами. Вы не должны использовать Integer, BTW. Используйте простой объект:

final Object sharedLock = new Object(); 
Thread a = new Thread(new Experimenter(sharedLock), "a"); 
Thread b = new Thread(new Experimenter(sharedLock), "b"); 
... 

Использование конечного ключевого слова: Это не строго необходимо. Но блокировки, особенно когда они объявлены как поля, должны быть окончательными, чтобы удостовериться, что вы не случайно присвоили новое значение переменной, что приведет к тому, что потоки будут использовать два разных объекта блокировки.

+0

Не могли бы вы объяснить необходимость в «финале»? – Hele

+0

Это не обязательно. Но блокировки, особенно когда они объявлены как поля, должны быть окончательными, чтобы удостовериться, что вы случайно не присвоили новую переменную переменной, что приведет к тому, что потоки будут использовать два разных объекта блокировки. –

+0

Не могли бы вы добавить это в свой ответ, чтобы помочь будущим пользователям лучше? – Hele

1

это, безусловно, работать для вас

public void funk() throws InterruptedException 
{ 
    synchronized (Experimenter.class) 
    { 
     System.out.println("in"); 
     Thread.sleep(5000); 
     System.out.println("out"); 
    } 
} 
+0

, если вы используете блокировку уровня класса, тогда переменная должна быть статичной, как часть другого ответа !!! – Prashant

1

Объявляем поле a в static и он будет работать, как ожидалось. В вашем коде поле является членом экземпляра, поэтому у вас есть на самом деле пять разных мониторов.