2015-04-29 3 views
2

Просто пообщайтесь с многопоточным чтением, чтобы узнать больше об этом в отношении ключевого слова и других аспектов synchronized. Я написал простой класс и основной класс для вызова методов этого класса в разных потоках. Я видел то, что ожидал несколько раз (значения менялись на 2 вместо 1), но затем наткнулся на более крупный прыжок, который я не могу понять - кто-нибудь знает, как это произошло?Повсеместное многопоточное поведение (Java)

Вот мой код:

public class SeatCounter { 
    private int count = 0; 

    public int getSeatCount() { 
     return count; 
    } 

    public void bookSeat() { 
     count++; 
    } 

    public void unBookSeat() { 
     count--; 
    } 
} 

и

public class Main { 
    public static void main(String args[]) { 
     final SeatCounter c = new SeatCounter(); 

     Thread t1 = new Thread() { 
      public void run() { 
       while(true) { 
        c.bookSeat(); 
        System.out.println(c.getSeatCount()); 
       } 
      } 
     }; 

     Thread t2 = new Thread() { 
      public void run() { 
       while(true) { 
        c.unBookSeat(); 
        System.out.println(c.getSeatCount()); 
       } 
      } 
     }; 

     t1.start(); 
     t2.start(); 
    } 
} 

, и я получил это в какой-то момент в моей консоли:

-16066 
-16067 
-16068 
-16069 
-16031 
-16069 
-16068 
-16067 
-16066 

EDIT: Спасибо за быстрые ответы, я знаю об использовании синхронизации, я просто играл, чтобы понять, что пошло не так, что я не понимаю, как это изменилось (от -16069 до -16031) одним прыжком, так как оператор распечатки должен произойти с каждым изменением сохраненного значения и с двумя потоками без синхронизации, я бы предположил, что это будет означать не более 2 в значении.

+4

Вы не используете * синхронизацию *. Таким образом, * странный * результат равен *, как ожидалось * в соответствии с Java Memory Model – TheLostMind

+3

Вы действительно не должны иметь никаких ожиданий относительно результата этого вычисления - каждый поток может выполняться в течение произвольного промежутка времени. Один поток, выполняющий 16 000 дополнительных декрементов, чем приращения в другом потоке, действительно не является неожиданным с учетом скорости процессоров и изменения времени выполнения среди потоков. – Oli

+0

Хорошо, должен был упомянуть - я знаю о синхронизированном ключевом слове, я просто рассматривал проблемы, не используя его. Я ожидал, что скачки 2 или 0 могут произойти, это большой прыжок, который меня смущает, так как не более 2 изменений должны произойти со значением до появления заявления печати. Кроме того, это было взято из небольшого количества времени, и я не ожидал, что он сидит около 0. –

ответ

2

Есть две вещи, которые могут вызвать «странные» подсчеты.

  • Вполне возможно, для подсчета ++ count-- вызывает сбой работать, как ожидалось: поскольку кол ++ требует, чтобы значение счетчика для чтения, а затем увеличивается (внутренне: две операции), другой поток может испортить вокруг с вашими значениями после того, как вы их прочитали, но до того, как вы изменили и сохранили их, что приведет к гонке данных после изменения. Вы можете решить это с помощью synchronization или использовать AtomicInteger.
  • У вас нет гарантий относительно , когда код будет выполнен. В частности, thread1 может завершиться до начала Thread2 или наоборот, или и то, и другое может привести к странным виткам. Операционная система может выделять время процессора для потоков по своему усмотрению (и в многоядерных машинах они могут фактически выполняться параллельно). Пока существует только один поток, генерирующий поток, вы обычно не замечаете таких вещей; но происходит постоянный отток потоков, запуск, упреждение, запуск снова и т. д., пока компьютер работает; все они управляются планировщиком операционных систем.
+1

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

0

Поскольку вы используете несколько потоков без синхронизации, все возможно.

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

Что касается System.out.Println() - если вы хотите печатать каждую величину SeatCounter я хотел бы предложить вам изменить логику немного:

public class SeatCounter { 
    private Object lock = new Object(); 
    private int count = 0; 

    public SeatCounter() { 
     System.out.printf("%d\t%d <- initial value\n", System.nanoTime(), count); 
    } 

    public int bookSeat(Thread who) { 
     synchronized (lock) { 
      System.out.printf("%d\t%d <- %s\n", System.nanoTime(), count, who.getName()); 
      count++; 
      return count; 
     } 
    } 

    public int unBookSeat(Thread who) { 
     synchronized (lock) { 
      System.out.printf("%d\t%d <- %s\n", System.nanoTime(), count, who.getName()); 
      count--; 
      return count; 
     } 
    } 

    public static void main(String args[]) { 
     final SeatCounter c = new SeatCounter(); 

     Thread t1 = new Thread() { 
      public void run() { 
       while(true) { 
        c.bookSeat(this); 
       } 
      } 
     }; 

     Thread t2 = new Thread() { 
      public void run() { 
       while(true) { 
        c.unBookSeat(this); 
       } 
      } 
     }; 

     t1.start(); 
     t2.start(); 
    } 
} 

Вот некоторые интересные читает: На использование новых атомных переменных - см JavaDoc более Информация: https://docs.oracle.com/javase/tutorial/essential/concurrency/atomicvars.html

Хорошая нить здесь с пояснениями (кредиты @Tomasz Nurkiewicz): What is the difference between atomic/volatile/synchronized?

0

Вы не имеете никакого контроля над параллелизмом кол. Если 2 потока обращаются к одной и той же переменной одновременно, это вызывает неопределенное поведение. Также, когда вы пытаетесь получить доступ к консоли с помощью System.out.println(), вам необходимо заблокировать ее или вывести из строя заявления с чередованием и чередования строк. Блокировка - это объект, который гарантирует, что общая часть памяти не будет изменена одним потоком, в то время как другая пытается ее прочитать. Это то, что я думаю, вы хотите.

import java.util.concurrent.locks.Lock; 
public class Main { 
public static void main(String args[]) { 

    final SeatCounter c = new SeatCounter(); 
    Lock mylock = new Lock(); 
    Thread t1 = new Thread() { 
     public void run() { 
      while(true) { 
       mylock.lock(); 
       c.bookSeat(); 
       System.out.println(c.getSeatCount()); 
       mylock.unlock(); 
      } 
     } 
    }; 

    Thread t2 = new Thread() { 
     public void run() { 
      while(true) { 
       mylock.lock(); 
       c.unBookSeat(); 
       System.out.println(c.getSeatCount()); 
       mylock.unlock(); 
      } 
     } 
    }; 

    t1.start(); 
    t2.start(); 
} 
} 
Смежные вопросы