2012-06-27 6 views
0

Я учусь для сертификации Java, и я вижу этот пример из книги Моголов в:Зачем нужна статическая блокировка для синхронизации system.out.println()?

public class Smiley extends Thread 
{ 
    @Override 
    public void run() 
    { 
     while(true) 
     { 
      synchronized(this) 
      { 
       try 
       { 
        System.out.print(":"); 
        Thread.sleep(100); 
        System.out.print("-"); 
        Thread.sleep(100); 
        System.out.println(")"); 
        Thread.sleep(100); 
       } 
       catch(InterruptedException e) 
       { 
        e.printStackTrace(); 
       } 
      } 
     } 
    } 


    public static void main(String[] args) 
    { 
     new Smiley().start(); 
     new Smiley().start(); 
    } 
} 

Цель состоит в том, чтобы напечатать один смайлик :-) в каждой строке. Мой вопрос в том, почему синхронизация на экземпляре (это) не достигает этого? Почему нам нужно синхронизировать на статическом уровне?

Спасибо,

+1

Я знаю, что это всего лишь пример, но я хотел бы отметить, что вы обычно не хотят держать блокировки во время сна –

+0

@JohnKane Я согласен с вами, но я предполагаю, что это принудительно переключает контекст и поощряет потоки печатать смайлики с чередованием. –

+1

@ Jonathon Reinhart да Я тоже думал об этом, я просто хотел добавить это как дополнительную заметку для более общего случая. –

ответ

6

Поскольку обратите внимание, что функция основной() создает два классов смайлик. И каждый из них бежит по своей нитке. Так как они блокируются на this, они оба собираются немедленно закрепить замок, без конкуренции с другой нитью. В этом случае их схема блокировки synchronize(this) ничего не выполняет.

При работе с многопоточными проблемами вы должны думать «Что я пытаюсь защитить?» В этом случае вам необходимо защитить System.out, чтобы обеспечить доступ к нему в том порядке, в котором вы хотите. Поскольку System.out является статическим, вам нужен какой-то внешний замок, который каждый поток должен приобрести, прежде чем они смогут его написать.

Для достижения этой цели вы можете использовать ReentrantLock.

2

Пожалуйста, не используйте синхронизированный (это) - это плохая практика вообще
Как объяснялось выше - блокировка должна делиться между двумя потоками, и в этом случае блокировка является экземпляром каждого класса (то есть - объекты, созданные новым Smiley).
То, что вы должны иметь, является общей блокировкой, возможно, с использованием статической переменной, которая распределяется между всеми экземплярами одного и того же класса,
или передать блокировку в качестве параметра для CTOR смайлика.
дам пример для 2-го варианта, основанный по предложению @Jonathon Рейнхарт использовать возвратного Замок

public class Smiley extends Thread 
{ 
    private ReentantLock lock; 

    public Smiley(ReentrantLock lock) { 
     this.lock = lock; 
    } 

    @Override 
    public void run() 
    { 
    while(true) 
    { 
     try { 
      lock.lock(); 
      System.out.print(":"); 
      Thread.sleep(100); 
      System.out.print("-"); 
      Thread.sleep(100); 
      System.out.println(")"); 
      Thread.sleep(100); 
     } 
     catch(InterruptedException e) { 
       e.printStackTrace(); 
     } 
     finally { 
      lock.unlock(); 
     } 
    } 
    } 

} 


public static void main(String[] args) 
{ 
    ReentrantLock lock = new ReentantLock(); 
    new Smiley(lock).start(); 
    new Smiley(lock).start(); 
} 

Некоторые указатели -
а. не забывайте, что код разблокировки должен быть в предложении finally - это хорошая практика (вы также можете попробовать блок try и, наконец, заблокировать, без блока catch).
b. Вы можете рассмотреть вопрос о замене ReentrantLock с другими замками из пакета java.util.concurrent - в зависимости от потребностей

1

Это на самом деле два вопроса вы спрашиваете, и ответы на них:

  • Почему синхронизации на экземпляре (это) не достигает этого?

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

  • Зачем нам синхронизироваться на статическом уровне?

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

Самый простой способ добиться того, что вы хотите, чтобы синхронизировать System.out следующим образом:

@Override 
public void run() { 
    while (true) { 
     synchronized (System.out) { 
      try { 
       System.out.print(":"); 
       Thread.sleep(100); 
       System.out.print("-"); 
       Thread.sleep(100); 
       System.out.println(")"); 
       Thread.sleep(100); 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
     } 
    } 
} 
Смежные вопросы