нишки, достъпващи несинхронизирани методи в Java

мога ли да помоля да ми обясните как работят нишките и синхронизацията в Java?

Искам да напиша приложение с висока производителност. Вътре в това приложение чета данни от файлове в някои вложени класове, които всъщност са черупка около HashMap.

След като четенето на данните приключи, стартирам нишки, които трябва да преминат през данните и да извършат различни проверки върху тях. Нишките обаче никога не променят данните!

Ако мога да гарантирам (или поне да се опитам да гарантирам;), че моите нишки никога не променят данните, мога ли да ги използвам, извиквайки несинхронизирани методи на обекти, съдържащи данни?

Ако множество нишки имат достъп до несинхронизирания метод, който не променя полето на класа, но има някои вътрешни променливи, безопасно ли е?

изкуствен пример:

public class Data{
// this hash map is filled before I start threads
protected Map<Integer, Spike> allSpikes = new HashMap<Integer, Spike>();

public HashMap returnBigSpikes(){
     Map<Integer, Spike> bigSpikes = new HashMap<Integer, Spike>();

     for (Integer i: allSpikes.keySet()){
         if (allSpikes.get(i).spikeSize > 100){
         bigSpikes.put(i,allSpikes.get(i));
         }
     }

     return bigSpikes;
}
}

Безопасно ли е да се извиква НЕсинхронизиран метод returnBigSpikes() от нишки?

Сега разбирам, че подобни случаи на употреба са потенциално много опасни, тъй като е трудно да се контролират, че данните (напр. върнати bigSpikes) няма да бъдат променени. Но аз вече го внедрих и тествах по този начин и искам да знам дали мога да използвам резултатите от моето приложение сега и да променя архитектурата по-късно...

Какво се случва, ако направя методите синхронизирани? Ще се забави ли производителността на приложението до 1 CPU? Ако е така, как мога да го проектирам правилно и да запазя производителността?

(Прочетох около 20-40 Gb данни (регистрационни съобщения) в основната памет и след това стартирам нишки, които трябва да преминат през всички данни, за да намерят някаква корелация в тях; всяка нишка става само част от съобщения за анализ; но за анализа нишката трябва да сравнява всяко съобщение от своята част с много други съобщения от данни; затова първо реших да разреша на нишките да четат данни без синхронизация).

Благодаря много предварително.


person Andrey Sapegin    schedule 21.03.2013    source източник
comment
има ли достъп до returnBigSpikes от множество нишки наведнъж?   -  person Vishal K    schedule 21.03.2013
comment
да това не е реален пример, но моето приложение вече работи по този начин.   -  person Andrey Sapegin    schedule 21.03.2013
comment
Мисля, че вашият код ще работи безопасно без използване на синхронизация.   -  person Vishal K    schedule 21.03.2013


Отговори (6)


Ако allSpikes е попълнен преди стартирането на всички нишки, можете да се уверите, че няма да бъде променен по-късно, като го запазите като непроменяема карта.

Ако приемем, че Spike е неизменен, тогава вашият метод би бил напълно безопасен за едновременно използване.

person Keppil    schedule 21.03.2013
comment
+1 за споменаването, че Спайк трябва да е неизменен/безопасен за нишки. Това е потенциалният проблем тук. - person Bruno Grieder; 21.03.2013

Като цяло, ако имате куп нишки, където можете да гарантирате, че само една нишка ще модифицира ресурс, а останалите само ще четат този ресурс, тогава достъпът до този ресурс не е необходимо да се синхронизира. Във вашия пример всеки път, когато методът returnBigSpikes() се извиква, той създава ново локално копие на bigSpikes hashmap, така че въпреки че създавате hashmap, той е уникален за всяка нишка, така че няма проблеми със синхронизирането там.

person Rich    schedule 21.03.2013
comment
Като цяло, ако имате куп нишки, където можете да гарантирате, че само една нишка ще модифицира ресурс, а останалите ще четат само този ресурс, тогава достъпът до този ресурс не е необходимо да се синхронизира - Не е съвсем неправилно. Тази ситуация може да причини състояние на състезание. Ако искате да кажете, че записът се появява първо (преди всяка друга нишка), тогава ВСИЧКИ четения се случват СЛЕД всеки запис, тогава ок. - person xagyg; 22.03.2013

Докато нещо практически неизменно (напр. използване на ключова дума final) и използвате unmodifiableMap, всичко е наред.

Бих предложил следните UnmodifiableData:

public class UnmodifiableData {
     final Map<Integer,Spike>  bigSpikes;
     public UnmodifiableData(Map<Integer,Spike> bigSpikes) {
         this.bigSpikes = Collections.unmodifiableMap(new HashMap<>(bigSpikes));
     }
     ....

}

person Chrstian Beutenmueller    schedule 22.03.2013

Вашият план трябва да работи добре. Не е нужно да synchronize четете, а само да пишете.

Ако обаче в бъдеще искате да кеширате bigSpikes, така че всички нишки да получават една и съща карта, тогава трябва да сте по-внимателни относно синхронизирането.

person OldCurmudgeon    schedule 21.03.2013
comment
това не беше истински пример... Въпреки това, дори ако всички нишки получат една и съща карта, но въпреки това не я променят, пак е безопасно, нали? - person Andrey Sapegin; 21.03.2013
comment
Да, безопасно е. Сложността възниква, ако кеширате по някакъв начин подкартите (като bigSpikes във вашата проба). Трябва да направите кешовете volatile и да обмислите какво се случва, когато две нишки започнат да изграждат кешираната карта едновременно. Ако изградите всички подкарти, преди да стартирате нишките, това отново не е проблем. - person OldCurmudgeon; 21.03.2013

Ако използвате ConcurrentHashMap, той ще свърши цялата работа по синхронизиране вместо вас. Това е bettr, след което се прави синхронизация около обикновен HashMap.

person Mikhail    schedule 21.03.2013
comment
да, но бих могъл да имам и други структури. И въпросът ми е по-общ, искам също да знам дали мога да използвам такъв дизайн в бъдеще. Няма ли ConcurrentHashMap да забави приложението ми по време на четене/получаване? - person Andrey Sapegin; 21.03.2013
comment
На практика ще работи безпроблемно, ако четете и не променяте данни. На теория вие нарушавате правилото на модела на паметта на Java, което е лоша практика. Ако не харесвате CuncurrentHashMap, погледнете ReentrantReadWriteLock. - person Mikhail; 21.03.2013

Тъй като allSpikes се инициализира преди да започнете нишки, това е безопасно. Проблеми с паралелността се появяват само когато една нишка пише в ресурс и други четат от нея.

person Evgeniy Dorofeev    schedule 21.03.2013
comment
и какво ще кажете за променливите вътре в несинхронизирания метод? Например „Map‹Integer, Spike› bigSpikes“ и „Integer i“. Ще извика ли всяка нишка свой собствен екземпляр на метода? - person Andrey Sapegin; 21.03.2013