अणु ऑपरेशन्सच्या उदयासाठी पूर्व-आवश्यकता

अणु ऑपरेशन्स कसे कार्य करतात हे समजण्यास मदत करण्यासाठी या उदाहरणावर एक नजर टाकूया:

public class Counter {
    int count;

    public void increment() {
        count++;
    }
}

जेव्हा आमच्याकडे एक थ्रेड असतो, तेव्हा सर्वकाही चांगले कार्य करते, परंतु आम्ही मल्टीथ्रेडिंग जोडल्यास, आम्हाला चुकीचे परिणाम मिळतात आणि सर्व कारण वाढीव ऑपरेशन हे एक ऑपरेशन नसून तीन आहे: वर्तमान मूल्य मिळविण्याची विनंतीमोजणे, नंतर ते 1 ने वाढवा आणि पुन्हा लिहामोजणे.

आणि जेव्हा दोन थ्रेड्स व्हेरिएबल वाढवू इच्छितात, तेव्हा तुम्ही बहुधा डेटा गमावाल. म्हणजेच, दोन्ही थ्रेड्स 100 प्राप्त करतात, परिणामी, दोन्ही 102 च्या अपेक्षित मूल्याऐवजी 101 लिहितात.

आणि ते कसे सोडवायचे? आपल्याला लॉक वापरण्याची आवश्यकता आहे. सिंक्रोनाइझ केलेला कीवर्ड या समस्येचे निराकरण करण्यात मदत करतो, त्याचा वापर करून तुम्हाला हमी मिळते की एका वेळी एक थ्रेड या पद्धतीमध्ये प्रवेश करेल.

public class SynchronizedCounterWithLock {
    private volatile int count;

    public synchronized void increment() {
        count++;
    }
}

शिवाय, तुम्हाला अस्थिर कीवर्ड जोडणे आवश्यक आहे , जे थ्रेडमधील संदर्भांची योग्य दृश्यमानता सुनिश्चित करते. आम्ही वर त्यांच्या कार्याचा आढावा घेतला आहे.

पण तरीही तोटे आहेत. सर्वात मोठी म्हणजे कामगिरी, ज्या वेळी अनेक थ्रेड लॉक मिळवण्याचा प्रयत्न करत असतात आणि एखाद्याला लिहिण्याची संधी मिळते, तेव्हा बाकीचे धागे एकतर ब्लॉक केले जातील किंवा थ्रेड रिलीझ होईपर्यंत निलंबित केले जातील.

या सर्व प्रक्रिया, अवरोधित करणे, दुसर्या स्थितीवर स्विच करणे सिस्टम कार्यक्षमतेसाठी खूप महाग आहेत.

अणु ऑपरेशन्स

अल्गोरिदम कमी-स्तरीय मशीन निर्देशांचा वापर करते जसे की तुलना-आणि-स्वॅप (CAS, तुलना-आणि-स्वॅप, जे डेटा अखंडता सुनिश्चित करते आणि त्यावर आधीपासूनच मोठ्या प्रमाणात संशोधन आहे).

एक सामान्य CAS ऑपरेशन तीन ऑपरेंडवर चालते:

  • कामासाठी मेमरी स्पेस (M)
  • व्हेरिएबलचे विद्यमान अपेक्षित मूल्य (A).
  • नवीन मूल्य (B) सेट करायचे आहे

CAS आण्विकरित्या M ते B मध्ये अद्यतनित करते, परंतु M चे मूल्य A सारखे असेल तरच, अन्यथा कोणतीही कारवाई केली जात नाही.

पहिल्या आणि दुसर्‍या प्रकरणांमध्ये, M चे मूल्य परत केले जाईल. हे तुम्हाला तीन चरण एकत्र करण्यास अनुमती देते, म्हणजे, मूल्य मिळवणे, मूल्याची तुलना करणे आणि ते अद्यतनित करणे. आणि हे सर्व मशीन स्तरावर एका ऑपरेशनमध्ये बदलते.

ज्या क्षणी एक मल्टी-थ्रेडेड ऍप्लिकेशन व्हेरिएबल ऍक्सेस करते आणि ते अपडेट करण्याचा प्रयत्न करते आणि CAS लागू केले जाते, तेव्हा थ्रेडपैकी एकाला ते मिळेल आणि ते अपडेट करण्यास सक्षम असेल. परंतु लॉकच्या विपरीत, इतर थ्रेड्सना फक्त मूल्य अद्यतनित करण्यात सक्षम नसल्याबद्दल त्रुटी प्राप्त होतील. मग ते पुढील कामाकडे जातील आणि या प्रकारच्या कामात स्विचिंग पूर्णपणे वगळण्यात आले आहे.

या प्रकरणात, CAS ऑपरेशन यशस्वीरित्या कार्य करत नसताना आम्हाला परिस्थिती हाताळावी लागते या वस्तुस्थितीमुळे तर्क अधिक कठीण होते. आम्ही फक्त कोडचे मॉडेल बनवू जेणेकरून ऑपरेशन यशस्वी होईपर्यंत तो पुढे सरकणार नाही.

अणु प्रकारांचा परिचय

int च्या सोप्या व्हेरिएबलसाठी तुम्हाला सिंक्रोनाइझेशन सेट करावे लागेल अशी परिस्थिती तुम्हाला आली आहे का ?

आम्ही आधीच कव्हर केलेला पहिला मार्ग म्हणजे volatile + synchronized वापरणे . पण विशेष अणु* वर्ग देखील आहेत.

आम्ही CAS वापरल्यास, पहिल्या पद्धतीच्या तुलनेत ऑपरेशन्स जलद कार्य करतात. आणि या व्यतिरिक्त, आमच्याकडे मूल्य जोडण्यासाठी आणि वाढ आणि कमी करण्याच्या ऑपरेशनसाठी विशेष आणि अतिशय सोयीस्कर पद्धती आहेत.

AtomicBoolean , AtomicInteger , AtomicLong , AtomicIntegerArray , AtomicLongArray हे असे वर्ग आहेत ज्यात ऑपरेशन्स अणू असतात. खाली आम्ही त्यांच्यासह कामाचे विश्लेषण करू.

AtomicInteger

AtomicInteger वर्ग विस्तारित अणु ऑपरेशन्स प्रदान करण्याव्यतिरिक्त, अणु वाचता आणि लिहिल्या जाऊ शकणार्‍या इंट मूल्यावर ऑपरेशन्स प्रदान करतो.

यात गेट आणि सेट पद्धती आहेत ज्या वाचन आणि लेखन व्हेरिएबल्स सारख्या कार्य करतात.

म्हणजेच, आपण आधी बोललो होतो त्याच व्हेरिएबलच्या कोणत्याही नंतरच्या पावतीसह “होते-आधी”. अणु compareAndSet पद्धतीमध्ये ही मेमरी सातत्य वैशिष्ट्ये देखील आहेत.

नवीन मूल्य परत करणारी सर्व ऑपरेशन्स अणू पद्धतीने केली जातात:

int addAndGet (इंट डेल्टा) वर्तमान मूल्यामध्ये विशिष्ट मूल्य जोडते.
बुलियन compareAndSet(अपेक्षित इंट, अपडेट इंट) वर्तमान मूल्य अपेक्षित मूल्याशी जुळल्यास दिलेल्या अद्यतनित मूल्यावर मूल्य सेट करते.
int decrementAndGet() वर्तमान मूल्य एकाने कमी करते.
int getAndAdd(इंट डेल्टा) दिलेले मूल्य वर्तमान मूल्यामध्ये जोडते.
int getAndDecrement() वर्तमान मूल्य एकाने कमी करते.
int getAndIncrement() वर्तमान मूल्य एकाने वाढवते.
int getAndSet(int newValue) दिलेले मूल्य सेट करते आणि जुने मूल्य परत करते.
int incrementAndGet() वर्तमान मूल्य एकाने वाढवते.
lazySet(int newValue) शेवटी दिलेल्या मूल्यावर सेट करा.
बूलियन कमकुवत CompareAndSet(अपेक्षित, अपडेट इंट) वर्तमान मूल्य अपेक्षित मूल्याशी जुळल्यास दिलेल्या अद्यतनित मूल्यावर मूल्य सेट करते.

उदाहरण:

ExecutorService executor = Executors.newFixedThreadPool(5);
IntStream.range(0, 50).forEach(i -> executor.submit(atomicInteger::incrementAndGet));
executor.shutdown();
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.HOURS);

System.out.println(atomicInteger.get()); // prints 50