Почему count1 < count2 после выполнения следующего кода? И count1, и count2 должны быть 2000000

#java #multithreading #java-11 #atomicinteger #synchronized-block

#java #многопоточность #java-11 #atomicinteger #синхронизированный блок

Вопрос:

Скомпилируйте и запустите в JDK11 и посмотрите на результаты. sum1 — это время, затрачиваемое синхронизированным кодом, а sum2 — это время для кода AtomicInteger. count1 является результатом подсчета количества вызовов синхронизированного count . count2 — это то же количество комбинированных вызовов, но с использованием AtomicInteger . Количество отсчетов должно быть 2000000, и ожидается, что sum1> sum2 в мс. Однако count1 значительно меньше, так куда идут вызовы?

github

 package com.charlie;

import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicTest extends Thread{

    private volatile Integer count1 = 0;
    private AtomicInteger count2 = new AtomicInteger(0);
    
    private void method1() {
        for (int i=0; i<1000; i  ) {
            int a = i;
        }
        synchronized (count1){
            count1  ;
        }
    }    
    private void method2() {
        for (int i=0; i<1000; i  ) {
            int a = i;
        }
        count2.getAndIncrement();
    }
    
    public class Thread1 extends Thread{
        public Date start = null;
        public Date stop = null;
        @Override
        public
        void run() {
            start = new Date();
            for(int i=0;i<1000000;i  ) {
                method1();
            }
            stop = new Date();
        } 
    }    
    public class Thread3 extends Thread{
        public Date start = null;
        public Date stop = null;
        @Override
        public
        void run() {
            start = new Date();
            for(int i=0;i<1000000;i  ) {
                method2();
            }
            stop = new Date();
        } 
    }    
    static Thread1 t1 = null;
    static Thread1 t2 = null;
    static Thread3 t3 = null;
    static Thread3 t4 = null;
    static AtomicTest master = null;

    public static void main(String[] args) {
        AtomicTest master = new AtomicTest();
        t1 = master.new Thread1();
        t2 = master.new Thread1();
        t3 = master.new Thread3();
        t4 = master.new Thread3();
        master.start();
        t1.start();t2.start();t3.start();t4.start();
    }
    
    @Override
    public void run() {
        //stop and sum
        Boolean finished = false;
        while(!finished) {
            try {
                AtomicTest.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            if(t1.stop != null amp;amp; t2.stop != null amp;amp; t3.stop != null amp;amp; t4.stop != null) {
                Long sum1 = (t1.stop.getTime() - t1.start.getTime()) 
                  (t2.stop.getTime() - t2.start.getTime());
                Long sum2 = (t3.stop.getTime() - t3.start.getTime())
                  (t4.stop.getTime() - t4.start.getTime());
                finished = true;
                System.out.println("sum1 ="   sum1   "ms sum2 ="   sum2   "ms");
                System.out.println("count1 ="   count1   " count2 ="   count2);
            }
        }
    }
}
 

Ответ №1:

Не синхронизируйте объекты, которые могут быть использованы повторно. Прочитайте эту статью, если хотите узнать больше.

Простым решением, позволяющим избежать этого в вашем случае, является блокировка пустым объектом.

 private Object key = new Object();
private volatile Integer count1 = 0;

private void method1() {
    for (int i=0; i<1000; i  ) {
        int a = i;
    }
    synchronized (key){
        count1  ;
    }
}    
 

Комментарии:

1. Проблема не в том, что объект используется повторно, а в том, что переменная меняется , указывая на другой объект после каждого обновления. Вот почему также рекомендуется объявлять переменную, используемую для блокировки (т.е. key ) как final . Кстати, когда вы используете правильную синхронизацию, нет необходимости объявлять изменяемое поле volatile .

Ответ №2:

Целое число Java является неизменяемым, поэтому, когда вы используете для него, он создает новый объект. Если вы синхронизируете эти объекты, они будут иметь разные идентификаторы, следовательно, это не сработает. Чтобы исправить это, вы можете изменить целое число на обычный int (если хотите, не обязательно), но создать отдельный объект для синхронизации. например, private Object sync = new Object(); Переход с Integer на обычный int также должен ускорить процесс.

Это работает, изменения теперь в github. Спасибо Мадьяру Тамасу, а также Джейсону Сплашетту.