6

मैं स्कैला और कार्यात्मक प्रोग्रामिंग के लिए अपेक्षाकृत नया हूं, और मुझे यह विचार पसंद है कि अपरिवर्तनीय वस्तुओं का उपयोग करके मैं कई थ्रेड सुरक्षा त्रुटियों से बच सकता हूं। एक चीज अभी भी मुझे परेशान करती है, और यह शास्त्रीय उदाहरण है जो थ्रेड सुरक्षा - साझा काउंटर को पढ़ाने के लिए प्रयोग किया जाता है।थ्रेड सुरक्षित साझा काउंटर को लागू करने के लिए कार्यात्मक तरीका

मैं सोच रहा था कि क्या थ्रेड-सुरक्षित काउंटर (इस उदाहरण में एक अनुरोध काउंटर) लागू करना संभव होगा, अपरिवर्तनीय वस्तुओं का उपयोग करके, और कार्यात्मक अवधारणाओं का उपयोग करना, और सिंक्रनाइज़ेशन से पूरी तरह से बचें।

:

संदर्भ के लिए

तो यहाँ पहले काउंटर के शास्त्रीय परिवर्तनशील संस्करणों (मुझे सार्वजनिक सदस्य चर के लिए बहाना, बस उदाहरण के संक्षिप्तता के लिए)

परिवर्त्य, गैर धागा सुरक्षित संस्करण हैं

public class Servlet extends HttpServlet { public int requestCount = 0; @Override public void service(ServletRequest req, ServletResponse res) throws ... { requestCount++; //thread unsafe super.service(req, res); } } 

परिवर्त्य, क्लासिक धागा सुरक्षित संस्करण: (या तो मुझे आशा है कि ...)

public class Servlet extends HttpServlet { 

    public volatile int requestCount = 0; 

    @Override 
    public void service(ServletRequest req, ServletResponse res) throws ... { 
    synchronized (this) { 
     requestCount++; 
    } 
    super.service(req, res); 
    } 
} 

मैं सोच रहा था कि सिंक्रनाइज़ेशन के बिना थ्रेड सुरक्षा प्राप्त करने के लिए अपरिवर्तनीय ऑब्जेक्ट्स और अस्थिर चर का उपयोग करने का कोई तरीका है या नहीं।

तो यहां मेरा निष्पक्ष प्रयास था। विचार है कि काउंटर के लिए एक अपरिवर्तनीय वस्तु हो, और अस्थिर चर का उपयोग करके संदर्भ को प्रतिस्थापित करें। फिश लग रहा है, लेकिन एक शॉट के लायक है।

धारक:

public class Incrementer { 
    private final int value; 
    public Incrementer(final int oldValue) { 
    this.value = oldValue + 1; 
    } 

    public Incrementer() { 
    this.value = 0; 
    } 

    public int getValue() { 
    return value; 
    } 
} 

संशोधित सर्वलेट:

public class Servlet extends HttpServlet { 

    public volatile Incrementer incrementer = new Incrementer(); 

    @Override 
    public void service(ServletRequest req, ServletResponse res) throws ... { 
    incrementer = new Incrementer(incrementer.getValue()); 
    super.service(req, res); 
    } 
} 

मैं एक मजबूत लग रहा है यह भी सुरक्षित थ्रेड नहीं है, जैसा कि मैंने incrementer से पढ़ रहा हूँ, और मिल सकता है एक पुराना मूल्य (उदाहरण के लिए यदि संदर्भ पहले से ही किसी अन्य धागे द्वारा प्रतिस्थापित किया गया था)। यदि यह वास्तव में धागा सुरक्षित नहीं है, तो मुझे आश्चर्य है कि लॉकिंग/सिंक्रनाइज़ेशन के बिना ऐसे काउंटर परिदृश्य को संभालने के लिए किसी भी "कार्यात्मक" तरीके से क्या है।

तो मेरे सवाल (रों)

  1. है इस सूत्र किसी भी संयोग से सुरक्षित हैं?
  2. यदि हां, क्यों?
  3. यदि नहीं, तो सिंक्रनाइज़ किए बिना ऐसे काउंटर को लागू करने के लिए किसी भी तरह से है?

हालांकि उदाहरण कोड ऊपर जावा में है, स्काला में उत्तर भी स्वागत

उत्तर

13

इस सूत्र किसी भी संयोग से सुरक्षित है निश्चित रूप से कर रहे हैं?

नहीं, जब तक कि आप पहले से ही सिंक्रनाइज़ किए गए ब्लॉक में अपरिवर्तनीय वस्तु नहीं बना चुके हैं, यह थ्रेड-सुरक्षित नहीं है। थ्रेड रेस हालत के तहत एक भ्रष्ट अपरिवर्तनीय वस्तु बनाने की संभावना है।

और समान कार्यक्षमता प्राप्त करने के लिए आप AtomicInteger का उपयोग कर सकते हैं जो स्पष्ट सिंक्रनाइज़ेशन से बचाता है।

public class Servlet extends HttpServlet { 

    public AtomicInteger incrementer = new AtomicInteger (0); 

    @Override 
    public void service(ServletRequest req, ServletResponse res) throws ... { 
    int newValue = incrementer.incrementAndGet(); 
    super.service(req, res); 
    } 
} 
+0

दिलचस्प, मुझे यकीन था कि परमाणु इंटेगर आंतरिक रूप से सिंक्रनाइज़ेशन का उपयोग करता है इसलिए मैंने इसे उत्तर के रूप में भी नहीं माना, लेकिन स्रोत कोड को देखते हुए ऐसा लगता है कि यह लक्ष्य प्राप्त करने के लिए कुछ अन्य तरीकों (मूल कोड) का उपयोग कर रहा है: http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/concurrent/atomic/AtomicInteger.java –

4

अपरिवर्तनीय से थ्रेड सुरक्षा इस तरह दिखती है।

val x = AtomicReference(Vector("salmon", "cod")) 

// Thread 1 
val y = x.get 
println(y(y.length-1)) 

// Thread 2 
x.getAndSet(x.get.tail) 

यदि आप mutably काम कर रहे थे, तो आप अत्यंत कष्ट थ्रेड 2 एक परिवर्तनशील सूची है, जो तब थ्रेड 1 के सूचकांक विफल कर सकता है बदल दिया है करने के लिए परीक्षा की जाएगी। या आपको डेटा की प्रतिलिपि बनाना होगा, जो कि बहुत महंगा हो सकता है यदि आपके पास व्यावहारिक रूप से पुन: उपयोग करने के लिए संग्रह नहीं था (और यदि वेक्टर लंबा था)। या आपको अपने डेटा को केवल परमाणु रूप से प्राप्त करने और/या प्राप्त करने के बजाय दोनों धागे में बड़े ब्लॉक को सिंक्रनाइज़ करना होगा।

आपको अभी भी किसी भी तरह सिंक्रनाइज़ करना होगा, और आपको डेटा की पुरानी प्रतियों से निपटना पड़ सकता है। लेकिन आप को ट्रैक रखना है कि किसके पास डेटा संरचना की एक प्रति है और पागल की तरह सभी को सिंक्रनाइज़ करें क्योंकि वह डेटा आपके नीचे से बदल सकता है और पूरे स्थान पर अपवाद फेंक सकता है।

संबंधित मुद्दे