जावा: एक्सेकॉर्स सर्विस जो एक निश्चित कतार आकार के बाद जमा करने पर रोकती है


85

मैं एक समाधान को कोड करने की कोशिश कर रहा हूं जिसमें एक एकल धागा I / O- गहन कार्यों का उत्पादन करता है जो समानांतर में किया जा सकता है। प्रत्येक कार्य में महत्वपूर्ण मेमोरी डेटा होता है। इसलिए मैं उन कार्यों को सीमित करना चाहता हूं जो एक पल में लंबित हैं।

अगर मैं इस तरह ThreadPoolExecutor बनाऊं:

    ThreadPoolExecutor executor = new ThreadPoolExecutor(numWorkerThreads, numWorkerThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>(maxQueue));

फिर executor.submit(callable) फेंकता है RejectedExecutionExceptionजब कतार भर जाती है और सभी धागे पहले से ही व्यस्त होते हैं।

बनाने के लिए मैं क्या कर सकता हूं executor.submit(callable)जब कतार भरी हो और सभी धागे व्यस्त हों ब्लॉक ?

संपादित करें : मैंने यह कोशिश की :

executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

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

EDIT: (प्रश्न पूछने के 5 साल बाद)

इस प्रश्न और इसके उत्तर को पढ़ने वाले किसी व्यक्ति के लिए, कृपया एक सही समाधान के रूप में स्वीकृत उत्तर को न लें। कृपया सभी उत्तर और टिप्पणियों के माध्यम से पढ़ें।



1
मैंने ठीक उसी तरह से करने के लिए पहले एक सेमाफोर का उपयोग किया है, जिस तरह से उसी तरह के प्रश्न @axtavt से जुड़ा हुआ है।
स्टीफन डेन्ने

2
@TomWolk एक चीज़ के लिए, आपको समानांतर में एक और कार्य निष्पादित करना होगा numWorkerThreadsजब कॉलर थ्रेड भी किसी कार्य को निष्पादित कर रहा हो। लेकिन, अधिक महत्वपूर्ण मुद्दा यह है कि यदि कॉलर थ्रेड को लंबे समय तक चलने वाला कार्य मिलता है, तो अन्य थ्रेड्स अगले कार्य की प्रतीक्षा में बेकार बैठ सकते हैं।
ताहिर अख्तर

2
@ ताहिरअख्तर, सच; क्यू को पर्याप्त रूप से लंबा होना चाहिए ताकि जब कॉल करने वाले को स्वयं कार्य निष्पादित करना पड़े तो यह सूखा न चले। लेकिन मुझे लगता है कि यह एक फायदा है अगर एक और धागा, कॉलर धागा, कार्यों को निष्पादित करने के लिए इस्तेमाल किया जा सकता है। यदि कॉलर सिर्फ ब्लॉक करता है, तो कॉल करने वाला का कार्य निष्क्रिय हो जाएगा। मैं CallerRunsPolicy का उपयोग थ्रेड की कैपाजिटी की तीन बार कतार के साथ करता हूं और यह अच्छा और सुचारू रूप से काम करता है। इस समाधान की तुलना में, मैं रूपरेखा अतिरंजना के साथ तड़के पर विचार करूंगा।
टॉमवॉक

1
@TomWalk +1 अच्छे अंक। ऐसा लगता है कि एक और अंतर यह है कि यदि कार्य कतार से अस्वीकार कर दिया गया था और कॉलर थ्रेड द्वारा चलाया गया था, तो कॉलर थ्रेड ऑर्डर से बाहर अनुरोध करना शुरू कर देगा क्योंकि यह कतार में अपनी बारी का इंतजार नहीं करता है। निश्चित रूप से, यदि आपने पहले से ही थ्रेड्स का उपयोग करने के लिए चुना है, तो आपको किसी भी निर्भरता को ठीक से संभालना होगा, लेकिन बस कुछ को ध्यान में रखना चाहिए।
रिमस्की

जवाबों:


64

मैंने भी यही किया है। ट्रिक एक ब्लॉकिंग क्यू बनाने के लिए है जहां प्रस्ताव () विधि वास्तव में एक पुट () है। (आप जो चाहें आधार ब्लॉकिंग का उपयोग कर सकते हैं, जो आप चाहते हैं)।

public class LimitedQueue<E> extends LinkedBlockingQueue<E> 
{
    public LimitedQueue(int maxSize)
    {
        super(maxSize);
    }

    @Override
    public boolean offer(E e)
    {
        // turn offer() and add() into a blocking calls (unless interrupted)
        try {
            put(e);
            return true;
        } catch(InterruptedException ie) {
            Thread.currentThread().interrupt();
        }
        return false;
    }

}

ध्यान दें कि यह केवल थ्रेड पूल के लिए काम करता है जहां पर corePoolSize==maxPoolSizeवहां सावधान रहें (टिप्पणियां देखें)।


2
वैकल्पिक रूप से आप बफ़रिंग को रोकने के लिए सिंक्रोनस क्यू का विस्तार कर सकते हैं, जिससे केवल प्रत्यक्ष हैंडऑफ़ की अनुमति मिलती है।
ब्रेंडन

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

5
मुझे नहीं लगता कि यह एक अच्छा विचार है क्योंकि यह प्रस्ताव पद्धति के प्रोटोकॉल को बदलता है। ऑफ़र विधि एक गैर-अवरोधक कॉल होनी चाहिए।
मिंगजियांग शि

6
मैं असहमत हूं - यह थ्रेडपूल एक्सक्यूटर के व्यवहार को बदल देता है। ऐसे करें कि यदि आपके पास एक कोरपूलिसाइज <maxPoolSize है, तो थ्रेडपूल एक्जिक्यूटर तर्क कोर से परे अतिरिक्त श्रमिकों को कभी नहीं जोड़ेगा।
क्रीज

5
स्पष्ट करने के लिए - आपका समाधान केवल तब तक काम करता है जब तक आप बाधा को बनाए रखते हैं corePoolSize==maxPoolSize। इसके बिना, यह अब थ्रेडपूल एक्सक्यूटर को डिज़ाइन किए गए व्यवहार की अनुमति नहीं देता है। मैं इस समस्या के समाधान की तलाश में था जो उस प्रतिबंध को नहीं लेती थी; नीचे दिए गए दृष्टिकोण के लिए मेरा वैकल्पिक उत्तर देखें।
क्रीज

15

यहाँ मैंने इसे अपने अंत में हल किया है:

(नोट: यह समाधान कॉल करने योग्य थ्रेड को ब्लॉक करता है, इसलिए यह RejectExecutionException को फेंकने से रोकता है)

public class BoundedExecutor extends ThreadPoolExecutor{

    private final Semaphore semaphore;

    public BoundedExecutor(int bound) {
        super(bound, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
        semaphore = new Semaphore(bound);
    }

    /**Submits task to execution pool, but blocks while number of running threads 
     * has reached the bound limit
     */
    public <T> Future<T> submitButBlockIfFull(final Callable<T> task) throws InterruptedException{

        semaphore.acquire();            
        return submit(task);                    
    }


    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);

        semaphore.release();
    }
}

1
मुझे लगता है कि यह उन मामलों के लिए अच्छा काम नहीं करता है जहाँ corePoolSize < maxPoolSize...:
रोज़गारपैक

1
यह उस मामले के लिए काम करता है जहां corePoolSize < maxPoolSize। उन मामलों में, सेमाफोर उपलब्ध होगा, लेकिन एक धागा नहीं होगा, और SynchronousQueueगलत वापस आ जाएगा। ThreadPoolExecutorफिर एक नया धागा घूमता रहेगा। इस समाधान की समस्या यह है कि इसकी एक दौड़ की स्थिति है । के बाद semaphore.release(), लेकिन थ्रेड फिनिशिंग से पहले execute, सबमिट करें () को सेमाफोर परमिट मिलेगा। यदिexecute() खत्म होने से पहले सुपर.सुमिट () चलाया जाता है , तो नौकरी अस्वीकार कर दी जाएगी।
लुइ गुइलहर्मे

@ LuísGuilherme लेकिन semaphore.release () धागा खत्म होने से पहले कभी नहीं बुलाया जाएगा । क्योंकि यह कॉल Execute (...) मेथड के बाद किया जाता है । क्या आप जिस परिदृश्य का वर्णन कर रहे हैं, उसमें मुझे कुछ याद आ रहा है?
cvacca

1
afterExecute उसी थ्रेड द्वारा कहा जाता है जो कार्य चलाता है, इसलिए यह अभी तक समाप्त नहीं हुआ है। टेस्ट खुद करें। उस समाधान को लागू करें, और निष्पादक पर बड़ी मात्रा में काम फेंक दें, अगर काम अस्वीकार कर दिया जाता है। आप देखेंगे कि हाँ, यह एक दौड़ की स्थिति है, और इसे पुन: पेश करना मुश्किल नहीं है।
लुइ गुइलहर्मे

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

14

वर्तमान में स्वीकार किए गए उत्तर में संभावित रूप से महत्वपूर्ण समस्या है - यह थ्रेडपूल एक्सक्यूटर के व्यवहार को बदल देता है। ऐसा करें कि यदि आपके पास ए corePoolSize < maxPoolSize, थ्रेडपूल एक्जिक्यूटर तर्क है तो कोर से परे अतिरिक्त श्रमिकों को कभी नहीं जोड़ा जाएगा।

से ThreadPoolExecutor .execute (Runnable):

    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    else if (!addWorker(command, false))
        reject(command);

विशेष रूप से, कि पिछले 'और' ब्लॉक विल कभी नहीं मारा जाएगा।

एक बेहतर विकल्प यह है कि ओपी पहले से जो कुछ कर रहा है, उसके समान ही करें - समान तर्क करने के लिए एक RejectExecutionHandler का उपयोग करें put:

public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
    try {
        if (!executor.isShutdown()) {
            executor.getQueue().put(r);
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        throw new RejectedExecutionException("Executor was interrupted while the task was waiting to put on work queue", e);
    }
}

इस दृष्टिकोण के साथ देखने के लिए कुछ चीजें हैं, जैसा कि टिप्पणियों में बताया गया है ( इस उत्तर का संदर्भ देते हुए ):

  1. यदि corePoolSize==0, तो एक दौड़ की स्थिति है जहां पूल में सभी धागे कार्य दिखाई देने से पहले मर सकते हैं
  2. एक कार्यान्वयन का उपयोग करना जो कतार के कार्यों को लपेटता है (लागू नहीं है ThreadPoolExecutor) उन मुद्दों पर परिणाम देगा जब तक कि हैंडलर भी उसी तरह से लपेटता नहीं है।

उन गोचरों को ध्यान में रखते हुए, यह समाधान सबसे विशिष्ट थ्रेडपूल एक्सक्यूटर्स के लिए काम करेगा, और जहां मामले को ठीक से संभाल लेगा corePoolSize < maxPoolSize


जिस किसी ने भी अपमानित किया - क्या आप कुछ अंतर्दृष्टि प्रदान कर सकते हैं? क्या इस उत्तर में कुछ गलत / भ्रामक / खतरनाक है? मैं आपकी चिंताओं को दूर करने का अवसर चाहूंगा।
क्रीज

2
मैंने
नीचा

@vanOekel - लिंक के लिए धन्यवाद - यह उत्तर कुछ मान्य मामलों को उठाता है जिन्हें इस दृष्टिकोण का उपयोग करने पर ज्ञात होना चाहिए, लेकिन IMO इसे "बहुत बुरा विचार" नहीं बनाता है - यह अभी भी वर्तमान में स्वीकृत उत्तर में मौजूद एक समस्या को हल करता है। मैंने अपना जवाब उन कैवियट के साथ अपडेट किया है।
क्रीज

यदि कोर पूल का आकार 0 है, और यदि कार्य निष्पादनकर्ता को प्रस्तुत किया जाता है, तो यदि कार्य को संभालने के लिए कतार पूरी हो जाती है, तो निष्पादक थ्रेड / एस बनाना शुरू कर देगा। फिर यह गतिरोध क्यों है। आपकी बात नहीं मिली क्या आप विस्तृत कर सकते हैं?
फरहान शिरगिल अंसारी

@ShirgillFarhanAnsari - यह पिछली टिप्पणी में उठाया गया मामला है। ऐसा इसलिए हो सकता है क्योंकि कतार में सीधे जोड़ने से थ्रेड / स्टार्टिंग वर्कर बनाने में ट्रिगर नहीं होता है। यह एक किनारे का मामला / दौड़ की स्थिति है जिसे एक गैर-शून्य कोर पूल के आकार के द्वारा कम किया जा सकता है
क्रीज

4

मुझे पता है कि यह एक पुराना सवाल है, लेकिन एक समान मुद्दा यह था कि नए कार्यों का निर्माण बहुत तेज था और अगर बहुत सारे आउटऑफमेरीऑरर होते हैं, क्योंकि मौजूदा कार्य तेजी से पूरा नहीं हुआ था।

मेरे मामले में Callables में प्रस्तुत किए जाते हैं और मुझे परिणाम की आवश्यकता होती है इसलिए मुझे सभी Futuresलौटे हुए स्टोर करने की आवश्यकता होती है executor.submit()। मेरा समाधान अधिकतम आकार के साथ Futuresमें डालना था BlockingQueue। एक बार जब कतार पूरी हो जाती है, तो कुछ पूर्ण होने तक अधिक कार्य नहीं किए जाते हैं (कतार से हटाए गए तत्व)। छद्म कोड में:

final ExecutorService executor = Executors.newFixedThreadPool(numWorkerThreads);
final LinkedBlockingQueue<Future> futures = new LinkedBlockingQueue<>(maxQueueSize);
try {   
    Thread taskGenerator = new Thread() {
        @Override
        public void run() {
            while (reader.hasNext) {
                Callable task = generateTask(reader.next());
                Future future = executor.submit(task);
                try {
                    // if queue is full blocks until a task
                    // is completed and hence no future tasks are submitted.
                    futures.put(compoundFuture);
                } catch (InterruptedException ex) {
                    Thread.currentThread().interrupt();         
                }
            }
        executor.shutdown();
        }
    }
    taskGenerator.start();

    // read from queue as long as task are being generated
    // or while Queue has elements in it
    while (taskGenerator.isAlive()
                    || !futures.isEmpty()) {
        Future compoundFuture = futures.take();
        // do something
    }
} catch (InterruptedException ex) {
    Thread.currentThread().interrupt();     
} catch (ExecutionException ex) {
    throw new MyException(ex);
} finally {
    executor.shutdownNow();
}

2

मुझे भी इसी तरह की समस्या थी और मैंने इसे beforeExecute/afterExecuteहुक से इस्तेमाल करके लागू कियाThreadPoolExecutor :

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Blocks current task execution if there is not enough resources for it.
 * Maximum task count usage controlled by maxTaskCount property.
 */
public class BlockingThreadPoolExecutor extends ThreadPoolExecutor {

    private final ReentrantLock taskLock = new ReentrantLock();
    private final Condition unpaused = taskLock.newCondition();
    private final int maxTaskCount;

    private volatile int currentTaskCount;

    public BlockingThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
            long keepAliveTime, TimeUnit unit,
            BlockingQueue<Runnable> workQueue, int maxTaskCount) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
        this.maxTaskCount = maxTaskCount;
    }

    /**
     * Executes task if there is enough system resources for it. Otherwise
     * waits.
     */
    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
        taskLock.lock();
        try {
            // Spin while we will not have enough capacity for this job
            while (maxTaskCount < currentTaskCount) {
                try {
                    unpaused.await();
                } catch (InterruptedException e) {
                    t.interrupt();
                }
            }
            currentTaskCount++;
        } finally {
            taskLock.unlock();
        }
    }

    /**
     * Signalling that one more task is welcome
     */
    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        taskLock.lock();
        try {
            currentTaskCount--;
            unpaused.signalAll();
        } finally {
            taskLock.unlock();
        }
    }
}

यह आपके लिए काफी अच्छा होना चाहिए। Btw, मूल कार्यान्वयन कार्य आकार आधारित था क्योंकि एक कार्य दूसरे की तुलना में 100 गुना बड़ा हो सकता है और दो विशाल कार्यों को प्रस्तुत करना बॉक्स को मार सकता था, लेकिन एक बड़ा और बहुत छोटा चलना ठीक था। यदि आपके I / O- गहन कार्य लगभग एक ही आकार के हैं तो आप इस वर्ग का उपयोग कर सकते हैं, अन्यथा बस मुझे बताएं और मैं आकार आधारित कार्यान्वयन पोस्ट करूंगा।

PS आप ThreadPoolExecutorjavadoc की जांच करना चाहेंगे । यह डौग ली से वास्तव में अच्छा उपयोगकर्ता गाइड है कि इसे आसानी से कैसे अनुकूलित किया जा सकता है।


1
मैं सोच रहा हूँ कि क्या होगा जब एक थ्रेड लॉक को पूर्व में निष्पादित कर रहा है () और देखता है maxTaskCount < currentTaskCountऔर unpausedस्थिति पर इंतजार करना शुरू कर देता है । एक ही समय में एक और धागा कार्य के पूरा होने का संकेत देने के लिए afterExecute () में लॉक हासिल करने की कोशिश करता है। क्या यह गतिरोध नहीं होगा?
ताहिर अख्तर

1
मैंने यह भी देखा कि यह समाधान उस धागे को अवरुद्ध नहीं करेगा जो कतार में पूर्ण होने पर कार्यों को प्रस्तुत करता है। तो RejectedExecutionExceptionअभी भी संभव है।
ताहिर अख्तर

1
रेंट्रेंटलॉक / कंडीशन क्लासेस का सिमेंटिक, सिंक्रनाइज़ और प्रतीक्षा / सूचना प्रदान करने के समान है। जब स्थिति प्रतीक्षा विधियों को ताला कहा जाता है, तो जारी किया जाता है, इसलिए कोई गतिरोध नहीं होगा।
पेट्रो सेमेनीयुक

सही, यह ExecutorService कॉलर थ्रेड को अवरुद्ध किए बिना सबमिट करने के कार्यों को रोकता है। नौकरी बस जमा हो रही है और इसके लिए अतुल्यकालिक रूप से संसाधित किया जाएगा जब इसके लिए पर्याप्त सिस्टम संसाधन होंगे।
पेट्रो सेमेनीयुक

2

मैंने डेकोरेटर पैटर्न के बाद एक समाधान लागू किया है और निष्पादित कार्यों की संख्या को नियंत्रित करने के लिए एक सेमाफोर का उपयोग किया है। आप इसे किसी भी Executorऔर के साथ उपयोग कर सकते हैं :

  • चल रहे कार्यों की अधिकतम निर्दिष्ट करें
  • कार्य निष्पादन परमिट के लिए प्रतीक्षा करने के लिए अधिकतम समय-निर्धारण निर्दिष्ट करें (यदि समय-सीमा गुजरती है और कोई परमिट प्राप्त नहीं होता है, तो एक RejectedExecutionExceptionफेंक दिया जाता है)
import static java.util.concurrent.TimeUnit.MILLISECONDS;

import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.Semaphore;

import javax.annotation.Nonnull;

public class BlockingOnFullQueueExecutorDecorator implements Executor {

    private static final class PermitReleasingDecorator implements Runnable {

        @Nonnull
        private final Runnable delegate;

        @Nonnull
        private final Semaphore semaphore;

        private PermitReleasingDecorator(@Nonnull final Runnable task, @Nonnull final Semaphore semaphoreToRelease) {
            this.delegate = task;
            this.semaphore = semaphoreToRelease;
        }

        @Override
        public void run() {
            try {
                this.delegate.run();
            }
            finally {
                // however execution goes, release permit for next task
                this.semaphore.release();
            }
        }

        @Override
        public final String toString() {
            return String.format("%s[delegate='%s']", getClass().getSimpleName(), this.delegate);
        }
    }

    @Nonnull
    private final Semaphore taskLimit;

    @Nonnull
    private final Duration timeout;

    @Nonnull
    private final Executor delegate;

    public BlockingOnFullQueueExecutorDecorator(@Nonnull final Executor executor, final int maximumTaskNumber, @Nonnull final Duration maximumTimeout) {
        this.delegate = Objects.requireNonNull(executor, "'executor' must not be null");
        if (maximumTaskNumber < 1) {
            throw new IllegalArgumentException(String.format("At least one task must be permitted, not '%d'", maximumTaskNumber));
        }
        this.timeout = Objects.requireNonNull(maximumTimeout, "'maximumTimeout' must not be null");
        if (this.timeout.isNegative()) {
            throw new IllegalArgumentException("'maximumTimeout' must not be negative");
        }
        this.taskLimit = new Semaphore(maximumTaskNumber);
    }

    @Override
    public final void execute(final Runnable command) {
        Objects.requireNonNull(command, "'command' must not be null");
        try {
            // attempt to acquire permit for task execution
            if (!this.taskLimit.tryAcquire(this.timeout.toMillis(), MILLISECONDS)) {
                throw new RejectedExecutionException(String.format("Executor '%s' busy", this.delegate));
            }
        }
        catch (final InterruptedException e) {
            // restore interrupt status
            Thread.currentThread().interrupt();
            throw new IllegalStateException(e);
        }

        this.delegate.execute(new PermitReleasingDecorator(command, this.taskLimit));
    }

    @Override
    public final String toString() {
        return String.format("%s[availablePermits='%s',timeout='%s',delegate='%s']", getClass().getSimpleName(), this.taskLimit.availablePermits(),
                this.timeout, this.delegate);
    }
}

1

मुझे लगता है कि यह ArrayBlockingQueueआ के बजाय का उपयोग करने के रूप में सरल हैLinkedBlockingQueue

मुझे अनदेखा करें ... यह पूरी तरह से गलत है। ThreadPoolExecutorकॉल Queue#offerनहींput जो आप की आवश्यकता होती है प्रभाव होगा।

आप के स्थान पर उस कॉल ThreadPoolExecutorका कार्यान्वयन और विस्तार कर सकते हैंexecute(Runnable)putoffer

मुझे लगता है कि मैं पूरी तरह से संतोषजनक जवाब की तरह नहीं हूँ।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.