কীভাবে সারিবদ্ধ হওয়ার আগে থ্রেডপুলকে আরও বাড়িয়ে তুলতে থ্রেডপুলএক্সিকিউটার পাবেন?


102

আমাদের বেশিরভাগ ব্যবহার করা থ্রেড-পুলগুলির ThreadPoolExecutorপিছনে থাকা ডিফল্ট আচরণে আমি কিছু সময়ের জন্য হতাশ হয়েছি ExecutorService। জাভাদোকস থেকে উদ্ধৃতি দিতে:

আরো corePoolSize চেয়ে কিন্তু চেয়ে maximumPoolSize থ্রেড চলমান কম হয়, তাহলে একটি নতুন থ্রেড তৈরি করা হবে শুধুমাত্র যদি কিউ পূর্ণ

এর অর্থ হ'ল আপনি যদি নীচের কোডটি দিয়ে কোনও থ্রেড পুলটি সংজ্ঞায়িত করেন তবে এটি কখনই দ্বিতীয় থ্রেড শুরু করবে না কারণ LinkedBlockingQueueসীমাহীন is

ExecutorService threadPool =
   new ThreadPoolExecutor(1 /*core*/, 50 /*max*/, 60 /*timeout*/,
      TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(/* unlimited queue */));

কেবলমাত্র যদি আপনার সীমাবদ্ধ সারি থাকে এবং সারিটি পূর্ণ থাকে তবে মূল সংখ্যার উপরে যে কোনও থ্রেড শুরু হবে। আমি সন্দেহ করি যে বিপুল সংখ্যক জুনিয়র জাভা মাল্ট্রিথ্রেড প্রোগ্রামাররা তাদের এই আচরণ সম্পর্কে অসচেতন ThreadPoolExecutor

এখন আমার নির্দিষ্ট ব্যবহারের কেস রয়েছে যেখানে এটি অনুকূল নয়। আমি নিজের টিপিপি ক্লাসটি না লিখে এটিকে ঘিরে কাজ করার উপায় খুঁজছি।

আমার প্রয়োজনীয়তাগুলি এমন কোনও ওয়েব পরিষেবার জন্য যা সম্ভবত অবিশ্বাস্য তৃতীয় পক্ষকে কল-ব্যাক করে।

  • আমি ওয়েব-অনুরোধের সাথে সিঙ্ক্রোনালি কল-ব্যাক করতে চাই না, তাই আমি একটি থ্রেড-পুল ব্যবহার করতে চাই।
  • আমি সাধারণত এক মিনিটের মধ্যে এই দুটি পাই তাই আমি newFixedThreadPool(...)বেশিরভাগ সুপ্ত থাকা থ্রেডের সাথে একটি রাখতে চাই না ।
  • প্রতিবার প্রায়শই আমি এই ট্র্যাফিকের বিস্ফোরণ ঘটায় এবং আমি থ্রেডের সংখ্যাটি সর্বাধিক মান পর্যন্ত বাড়িয়ে তুলতে চাই (আসুন 50 বলি)।
  • আমার সমস্ত কলব্যাক করার জন্য সর্বোত্তম চেষ্টা করা দরকার যাতে আমি 50 এর উপরে যে কোনও অতিরিক্ত ক্রিয়াকলাপ করতে চাই newCachedThreadPool()

আরও থ্রেড শুরুর আগেThreadPoolExecutor যেখানে সারি সীমাবদ্ধ করা এবং পূর্ণ হওয়া দরকার সেখানে এই সীমাবদ্ধতার আশেপাশে আমি কীভাবে কাজ করতে পারি ? কাজগুলি সারি করার আগে আমি আরও থ্রেড শুরু করতে কীভাবে এটি পেতে পারি ?

সম্পাদনা করুন:

ThreadPoolExecutor.allowCoreThreadTimeOut(true)মূল থ্রেডের সময়সীমা শেষ হওয়ার এবং প্রস্থান করার জন্য @ ফ্লাভিও একটি ভাল ধারণা দেয়। আমি এটি বিবেচনা করেছি কিন্তু আমি এখনও মূল থ্রেড বৈশিষ্ট্য চাই। আমি চাইনি যে পুলে থ্রেডের সংখ্যা সম্ভব হলে মূল-আকারের নীচে নেমে যায়।


4
আপনার উদাহরণটি সর্বাধিক 10 টি থ্রেড তৈরি করে, এমনটি কোনও স্থির আকারের থ্রেড পুলের উপরে বেড়ে ওঠা / সঙ্কুচিত এমন কোনও কিছু ব্যবহারের ক্ষেত্রে কি সত্যিকারের সঞ্চয় আছে?
বুস্টেম্পি

ভাল পয়েন্ট @bstempi। সংখ্যাটি কিছুটা নির্বিচারে ছিল। আমি প্রশ্নে এটি 50 টি করে বাড়িয়েছি। ঠিক এখনই ঠিক জানি না যে আমি আসলে কতটা সমকালীন থ্রেডে কাজ করতে চাইছি এখনই আমার কাছে এই সমাধান রয়েছে।
ধুসর

4
ওহ অভিশাপ! আমি এখানে থাকতে পারলে 10 টি আপভোট, ঠিক একই অবস্থানে যাচ্ছি আমি।
ইউজিন

উত্তর:


52

এই সীমাবদ্ধতার আশেপাশে আমি কীভাবে কাজ করতে পারি ThreadPoolExecutorযেখানে আরও থ্রেড শুরুর আগে সারি সীমাবদ্ধ করা এবং পূর্ণ করা দরকার।

আমি বিশ্বাস করি যে অবশেষে আমি এই সীমাবদ্ধতার কিছুটা মার্জিত (সম্ভবত কিছুটা হ্যাকি) সমাধান পেয়েছি ThreadPoolExecutor। এটা তোলে ব্যাপ্ত জড়িত LinkedBlockingQueueতা ফেরত আছে falseজন্য queue.offer(...)যখন ইতিমধ্যেই আছে কিছু সারিবদ্ধ কর্ম। যদি বর্তমান থ্রেডগুলি সারিবদ্ধ কাজগুলি না করে রাখে তবে টিপিই অতিরিক্ত থ্রেড যুক্ত করবে। যদি পুলটি ইতিমধ্যে সর্বোচ্চ থ্রেডে থাকে, তবে RejectedExecutionHandlerডাকা হবে। এটি হ্যান্ডলার যা put(...)কাতারে পরে কাজ করে ।

এটা অবশ্যই একটি সারিতে যেখানে লিখতে অদ্ভুত offer(...)আসতে পারেন falseএবং put()কখনো ব্লক যাতে হ্যাক অংশ। তবে এটি টিপিই-র কাতারের ব্যবহারের সাথে ভাল কাজ করে তাই আমি এটি করতে কোনও সমস্যা দেখতে পাচ্ছি না।

কোডটি এখানে:

// extend LinkedBlockingQueue to force offer() to return false conditionally
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>() {
    private static final long serialVersionUID = -6903933921423432194L;
    @Override
    public boolean offer(Runnable e) {
        // Offer it to the queue if there is 0 items already queued, else
        // return false so the TPE will add another thread. If we return false
        // and max threads have been reached then the RejectedExecutionHandler
        // will be called which will do the put into the queue.
        if (size() == 0) {
            return super.offer(e);
        } else {
            return false;
        }
    }
};
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1 /*core*/, 50 /*max*/,
        60 /*secs*/, TimeUnit.SECONDS, queue);
threadPool.setRejectedExecutionHandler(new RejectedExecutionHandler() {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        try {
            // This does the actual put into the queue. Once the max threads
            //  have been reached, the tasks will then queue up.
            executor.getQueue().put(r);
            // we do this after the put() to stop race conditions
            if (executor.isShutdown()) {
                throw new RejectedExecutionException(
                    "Task " + r + " rejected from " + e);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return;
        }
    }
});

এই প্রক্রিয়াটির সাথে, যখন আমি কাতারে কাজগুলি জমা দেই, তখন ThreadPoolExecutorউইল:

  1. প্রাথমিকভাবে মূল আকার পর্যন্ত থ্রেডের সংখ্যা স্কেল করুন (এখানে 1)।
  2. এটি কাতারে অফার করুন। যদি সারিটি খালি থাকে তবে এটি বিদ্যমান থ্রেডগুলি দ্বারা পরিচালনা করার জন্য সারি করা হবে।
  3. যদি সারিতে ইতিমধ্যে 1 বা ততোধিক উপাদান রয়েছে, তবে offer(...)মিথ্যাটি ফিরে আসবে।
  4. যদি মিথ্যাটি ফেরত দেওয়া হয়, যতক্ষণ না তারা সর্বাধিক সংখ্যায় পৌঁছায় (পঞ্চাশটি এখানে) পুলে থ্রেডের সংখ্যা বাড়িয়ে নিন।
  5. যদি সর্বোচ্চ হয় তবে এটি কল করে RejectedExecutionHandler
  6. RejectedExecutionHandlerকিউ তারপরে রাখে টাস্ক হিসাবে FIFO অনুক্রমে প্রথম উপলব্ধ থ্রেড দ্বারা প্রক্রিয়াকৃত হবে।

যদিও উপরে আমার উদাহরণ কোডে, সারিটি সীমাহীন নয়, আপনি এটি একটি সীমাবদ্ধ সারি হিসাবেও সংজ্ঞায়িত করতে পারেন। উদাহরণস্বরূপ, আপনি যদি 1000 এর সক্ষমতা যোগ করেন LinkedBlockingQueueতবে তা হবে:

  1. থ্রেড সর্বাধিক পর্যন্ত স্কেল করুন
  2. তারপরে এটি 1000 টি কার্য দিয়ে পূর্ণ না হওয়া পর্যন্ত সারি করুন
  3. তারপরে কলারটিকে অবধি অবধি সীমাবদ্ধ রাখুন যতক্ষণ না স্থানটি সারিতে উপলব্ধ থাকে।

এছাড়াও, যদি আপনি ব্যবহার করতে প্রয়োজন offer(...)মধ্যে RejectedExecutionHandlerতারপর আপনি ব্যবহার করতে পারে offer(E, long, TimeUnit)পরিবর্তে সঙ্গে পদ্ধতি Long.MAX_VALUEসময়সীমার হিসাবে।

সতর্কতা:

আপনি যদি শাটডাউন করার পরে নির্বাহকের সাথে কাজগুলি যুক্ত হওয়ার প্রত্যাশা করেন , তবে এক্সিকিউটর-পরিষেবাটি বন্ধ হয়ে যাওয়ার পরে আপনি RejectedExecutionExceptionআমাদের রীতিনীতিটি ছুঁড়ে দেওয়ার বিষয়ে আরও চৌকস হতে পারেন RejectedExecutionHandler। এটি নির্দেশ করার জন্য @ রাদুটোদারকে ধন্যবাদ।

সম্পাদনা করুন:

এই উত্তরের আর একটি ঝাপটায় টিপিইকে জিজ্ঞাসা করা যেতে পারে যদি সেখানে নিষ্ক্রিয় থ্রেড থাকে এবং কেবল যদি থাকে তবে আইটেমটিকে সারিবদ্ধ করুন। আপনাকে ourQueue.setThreadPoolExecutor(tpe);এটির জন্য একটি সত্য বর্গ তৈরি করতে হবে এবং এটিতে পদ্ধতি যুক্ত করতে হবে।

তারপরে আপনার offer(...)পদ্ধতিটি দেখতে দেখতে এমন কিছু হতে পারে:

  1. এই tpe.getPoolSize() == tpe.getMaximumPoolSize()ক্ষেত্রে কেবল কল করুন কিনা তা পরীক্ষা করে দেখুন super.offer(...)
  2. অন্যথায় যদি tpe.getPoolSize() > tpe.getActiveCount()তখন কল হয় super.offer(...)যেহেতু নিষ্ক্রিয় থ্রেড রয়েছে।
  3. অন্যথায় falseঅন্য থ্রেডে ফিরুন।

হয়তো এই:

int poolSize = tpe.getPoolSize();
int maximumPoolSize = tpe.getMaximumPoolSize();
if (poolSize >= maximumPoolSize || poolSize > tpe.getActiveCount()) {
    return super.offer(e);
} else {
    return false;
}

নোট করুন যে টিপিইতে প্রাপ্ত পদ্ধতিগুলি ব্যয়বহুল কারণ তারা volatileক্ষেত্রগুলিতে অ্যাক্সেস করে বা (ক্ষেত্রে getActiveCount()) টিপিই লক করে থ্রেড-তালিকায় চলে। এছাড়াও, এখানে জাতিগুলির শর্ত রয়েছে যা অলস থ্রেড থাকাকালীন কোনও কাজকে ভুলভাবে চিহ্নিত করতে পারে বা অন্য থ্রেড কাঁটাতে পারে।


আমিও একই সমস্যার সাথে লড়াই করেছি, এক্সিকিউট পদ্ধতিতে ওভাররাইড করে শেষ করেছি। তবে এটি সত্যিই একটি দুর্দান্ত সমাধান। :)
ব্যাটি

এটি Queueসম্পাদন করার জন্য চুক্তি ভঙ্গ করার মত ধারণাটি আমি যতটা পছন্দ করি না , আপনি অবশ্যই আপনার ধারণায় একা নন: গ্রোভি
প্রোগ্রামিং

4
আপনি কি এখানে বিজোড়তা পাবেন না যে প্রথম দু'টি কাজ সারিবদ্ধ হবে এবং কেবলমাত্র নতুন থ্রেডগুলি স্প্যান হওয়ার পরে? উদাহরণস্বরূপ, যদি আপনার একটি মূল থ্রেড কোনও একক দীর্ঘ-চলমান কার্যক্রমে ব্যস্ত থাকে এবং আপনি কল করেন execute(runnable), তবে runnableকেবল সারিটিতে যুক্ত করা হবে। যদি আপনি কল করেন execute(secondRunnable)তবে secondRunnableকাতারে যুক্ত করা হবে। তবে এখন যদি আপনি কল করেন execute(thirdRunnable)তবে thirdRunnableএকটি নতুন থ্রেডে চালানো হবে। runnableএবং secondRunnableশুধুমাত্র একবার চালানো thirdRunnable(অথবা মূল দীর্ঘক্ষন ধরে চলা কাজের) সমাপ্ত হয়।
রবার্ট টুপেলো-শ্নেক

4
হ্যাঁ, রবার্ট ঠিক আছে, একটি উচ্চ-বহু-বিস্তৃত পরিবেশে, মাঝে মাঝে সারি ক্রম বাড়তে থাকে যখন ব্যবহারের জন্য মুক্ত থ্রেড থাকে। নীচের সমাধানটি যা টিপিই প্রসারিত করে - আরও ভাল কাজ করে। আমি মনে করি রবার্টের পরামর্শটি উত্তর হিসাবে চিহ্নিত করা উচিত, যদিও উপরের হ্যাকটি আকর্ষণীয়
Wanna All All

4
"রিজেক্টেড এক্সেকিউশনহ্যান্ডলার" শাটডাউনে এক্সিকিউটারকে সহায়তা করেছিল। এখন আপনাকে শাটডাউন (() বন্ধ হিসাবে নতুন শব্দগুলি ব্যবহার করতে বাধ্য করা হচ্ছে) নতুন কার্যগুলি যুক্ত করা বাধা দেয় না (কারণ কারণে)
রাদু টডার

30

আমি ইতিমধ্যে এই প্রশ্নে অন্য দুটি উত্তর পেয়েছি, কিন্তু আমি সন্দেহ করি যে এটি সবচেয়ে ভাল।

এটি বর্তমানে গৃহীত উত্তরের কৌশলটির উপর ভিত্তি করে , যথা:

  1. offer()(কখনও কখনও) মিথ্যা প্রত্যাবর্তনের জন্য সারির পদ্ধতিটি ওভাররাইড করুন
  2. যার ফলে ThreadPoolExecutorহয় একটি নতুন থ্রেড ছড়িয়ে দেয় বা কাজটি প্রত্যাখ্যান করে এবং
  3. সেট RejectedExecutionHandlerথেকে আসলে প্রত্যাখ্যান উপর টাস্ক সারিতে।

সমস্যাটি কখন offer()মিথ্যা প্রত্যাবর্তন করা উচিত is বর্তমানে গৃহীত উত্তরগুলি মিথ্যা প্রত্যাবর্তন করে যখন সারিটিতে কয়েকটি কাজ করা থাকে তবে আমি সেখানে আমার মন্তব্যে উল্লেখ করেছি যে এটি অনাকাঙ্ক্ষিত প্রভাবের কারণ হয়। অন্যথায়, আপনি যদি সর্বদা মিথ্যা প্রত্যাবর্তন করেন তবে আপনার কাতারে থ্রেড থাকা অবস্থায়ও আপনি নতুন থ্রেড তৈরি করবেন aw

সমাধানটি জাভা 7 ব্যবহার করা LinkedTransferQueueএবং offer()কল করা tryTransfer()। যখন অপেক্ষারত গ্রাহক থ্রেড থাকবে তখন টাস্কটি কেবল সেই থ্রেডে চলে যাবে। অন্যথায়, offer()মিথ্যা ফিরে আসবে এবং ThreadPoolExecutorএকটি নতুন থ্রেড ছড়িয়ে দেবে।

    BlockingQueue<Runnable> queue = new LinkedTransferQueue<Runnable>() {
        @Override
        public boolean offer(Runnable e) {
            return tryTransfer(e);
        }
    };
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 50, 60, TimeUnit.SECONDS, queue);
    threadPool.setRejectedExecutionHandler(new RejectedExecutionHandler() {
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            try {
                executor.getQueue().put(r);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    });

আমার একমত হতে হবে, এটি আমার কাছে সবচেয়ে পরিষ্কার দেখাচ্ছে। সমাধানটির একমাত্র অবলম্বন হ'ল লিংকড ট্রান্সফারকিউ আনবাউন্ডেড যাতে আপনি অতিরিক্ত কাজ ছাড়াই সক্ষমতা-সীমাবদ্ধ টাস্কের সারি পান না।
ইয়ারোক

যখন পুলটি সর্বোচ্চ আকারে বৃদ্ধি পায় তখন একটি সমস্যা রয়েছে। বলুন যে পুলটি সর্বোচ্চ আকার পর্যন্ত মাপানো হয়েছে এবং প্রতিটি থ্রেড বর্তমানে কোনও কার্য সম্পাদন করছে, যখন রান রানযোগ্য এই প্রস্তাবটি প্রেরণ করা হবে তা মিথ্যা প্রত্যাবর্তন করবে এবং থ্রেডপুলঅ্যাক্সিকিউটর অ্যাড ওয়ার্কার থ্রেডের চেষ্টা করবে তবে পুলটি ইতিমধ্যে পৌঁছে গেছে এটি সর্বোচ্চ যাতে চালানো যায় কেবল তা প্রত্যাখাত হবে। প্রত্যাখ্যাত এক্সেক্স হ্যান্ডলার অনুসারে আপনি লিখেছেন এটি আবার কাতারে দেওয়া হবে যার ফলে এই বানর নাচ আবার শুরু থেকেই ঘটে।
সুধীরা 16

4
@ সুদিরা আমি বিশ্বাস করি আপনি ভুল করেছেন। queue.offer(), কারণ এটি প্রকৃতপক্ষে কল করছে LinkedTransferQueue.tryTransfer(), মিথ্যা ফিরিয়ে দেবে এবং কার্য সজ্জিত করবে না। তবে RejectedExecutionHandlerকলগুলি queue.put(), যা ব্যর্থ হয় না এবং কাজটি সজ্জিত করে না।
রবার্ট টুপেলো-শ্নেক

4
@ রবার্টটুপেলো-স্নেক অত্যন্ত কার্যকর এবং দুর্দান্ত!
ইউজিন

4
@ রবার্ট টুপেলো-শ্নেক একটি কবজির মতো কাজ করে! জাভাতে বক্সের বাইরে কেন এমন কিছু নেই তা আমি জানি না
জর্জি পিভ

28

মূল আকার এবং সর্বাধিক আকারকে একই মান হিসাবে সেট করুন এবং এর সাহায্যে পুল থেকে মূল থ্রেডগুলি সরানোর অনুমতি দিন allowCoreThreadTimeOut(true)


হ্যাঁ আমি এটি সম্পর্কে ভেবেছিলাম তবে আমি এখনও মূল থ্রেড বৈশিষ্ট্যটি পেতে চাই। সুস্থ সময়কালে থ্রেড পুলটি 0 টি থ্রেডে যেতে চাইনি। আমি আমার প্রশ্নটি এডিট করতে সম্পাদনা করব। তবে চমৎকার পয়েন্ট।
ধুসর

ধন্যবাদ! এটি করার সহজতম উপায়।
দিমিত্রি ওভচিনিকভ

7

দ্রষ্টব্য: আমি এখন আমার অন্য উত্তরটি পছন্দ এবং সুপারিশ করি

এখানে এমন একটি সংস্করণ যা আমার কাছে আরও সহজবোধ্য মনে হয়েছে: যখনই কোনও নতুন কার্য সম্পাদন করা হবে তখন কোরপুলসাইজ (সর্বোচ্চপুলসাইজের সীমা অবধি) বাড়ান, তারপরে কোরপুল সাইজ হ্রাস করুন (ব্যবহারকারী নির্দিষ্ট "মূল পুলের আকারের সীমা" নীচে নামিয়ে আনুন) টাস্ক সম্পূর্ণ

এটি অন্য উপায়ে রাখার জন্য, চলমান বা সজ্জিত কার্যগুলির সংখ্যাটি ট্র্যাক করে রাখুন এবং নিশ্চিত করুন যে কোরপুলসাইজ ব্যবহারকারী নির্দিষ্ট "কোর পুলের আকার" এবং সর্বাধিকপুল সাইজের মধ্যে যতক্ষণ কর্মের সংখ্যার সমান।

public class GrowBeforeQueueThreadPoolExecutor extends ThreadPoolExecutor {
    private int userSpecifiedCorePoolSize;
    private int taskCount;

    public GrowBeforeQueueThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
        userSpecifiedCorePoolSize = corePoolSize;
    }

    @Override
    public void execute(Runnable runnable) {
        synchronized (this) {
            taskCount++;
            setCorePoolSizeToTaskCountWithinBounds();
        }
        super.execute(runnable);
    }

    @Override
    protected void afterExecute(Runnable runnable, Throwable throwable) {
        super.afterExecute(runnable, throwable);
        synchronized (this) {
            taskCount--;
            setCorePoolSizeToTaskCountWithinBounds();
        }
    }

    private void setCorePoolSizeToTaskCountWithinBounds() {
        int threads = taskCount;
        if (threads < userSpecifiedCorePoolSize) threads = userSpecifiedCorePoolSize;
        if (threads > getMaximumPoolSize()) threads = getMaximumPoolSize();
        setCorePoolSize(threads);
    }
}

লিখিত হিসাবে ক্লাসটি নির্ধারিত পরে ব্যবহারকারী নির্দিষ্ট কোরপুলসাইজ বা সর্বোচ্চপুলসাইজ পরিবর্তন সমর্থন করে না এবং সরাসরি বা মাধ্যমে remove()বা মাধ্যমে কাজ সারিটি পরিচালনা করতে সমর্থন করে না purge()


synchronizedব্লক ব্যতীত আমি এটি পছন্দ করি । কাজের সংখ্যা পেতে আপনি কি সারিতে কল করতে পারেন? অথবা সম্ভবত একটি ব্যবহার AtomicInteger?
ধূসর

আমি এগুলি এড়াতে চেয়েছিলাম, তবে সমস্যাটি এটি। execute()পৃথক থ্রেডে যদি বেশ কয়েকটি কল হয় তবে প্রতিটি (1) কত সংখ্যক থ্রেডের প্রয়োজন তা নির্ধারণ করতে চলেছে, (2) setCorePoolSizeসেই নম্বরে, এবং (3) কল super.execute()। যদি পদক্ষেপগুলি (1) এবং (2) সিঙ্ক্রোনাইজ করা না হয় তবে আমি নিশ্চিত না যে কীভাবে কোনও দুর্ভাগ্যক্রমে ক্রম রোধ করতে হবে যেখানে আপনি মূল সংখ্যাকে উচ্চতর সংখ্যার পরে কম সংখ্যায় সেট করেছেন। সুপারক্লাস ফিল্ডে সরাসরি অ্যাক্সেসের সাথে এটি পরিবর্তে তুলনা-ও-সেট ব্যবহার করে করা যেতে পারে, তবে এটি সংলগ্নকরণ ছাড়া সাবক্লাসে এটি করার কোনও পরিষ্কার উপায় আমি দেখতে পাচ্ছি না।
রবার্ট টুপেলো-শ্নেক

আমি মনে করি যতক্ষণ taskCountক্ষেত্রটি বৈধ হয় (যেমন ক AtomicInteger) সেই জাতি শর্তের জন্য শাস্তি তুলনামূলকভাবে কম । দুটি থ্রেড যদি একে অপরের সাথে সাথেই পুলের আকারটি পুনঃসংযোগ করে, তবে এটির যথাযথ মান পাওয়া উচিত। যদি ২ য় একটি মূল থ্রেডগুলি সঙ্কুচিত করে তবে এটি অবশ্যই সারি বা কোনও কিছুর মধ্যে একটি ড্রপ দেখেছিল।
ধূসর

4
দুঃখের সাথে আমি মনে করি এটি এর থেকেও খারাপ। ধরুন 10 এবং 11 টি কার্য কল করুন execute()। প্রত্যেকে কল করবে atomicTaskCount.incrementAndGet()এবং তারা যথাক্রমে 10 এবং 11 পাবে। তবে সিঙ্ক্রোনাইজেশন ছাড়াই (টাস্কের গণনা অর্জন এবং কোর পুলের আকার নির্ধারণের পরে) আপনি পেতে পারেন (1) টাস্ক 11 মূল পুলের আকার 11 এ সেট করে, (2) টাস্ক 10 মূল পুলের আকার 10, (3) সেট করে টাস্ক 10 কল super.execute(), (4) টাস্ক 11 কল super.execute()এবং সমাপ্ত হয়।
রবার্ট টুপেলো-শ্নেক

4
আমি এই সমাধানটিকে কিছু গুরুতর পরীক্ষা দিয়েছি এবং এটি স্পষ্টতই সেরা। অত্যন্ত মাল্টিথ্রেডেড পরিবেশে এটি ফ্রি থ্রেড (ফ্রি-থ্রেডেড টিপিই.স্যাকিউট প্রকৃতির কারণে) থাকা অবস্থায় এখনও কখনও কখনও সজ্জিত হয়, তবে উত্তর-বর্ণিত সমাধানের বিপরীতে এটি খুব কমই ঘটে, যেখানে বর্ণের অবস্থার আরও সম্ভাবনা রয়েছে ঘটবে, সুতরাং প্রতিটি বহু-থ্রেডযুক্ত রানে এটি বেশ কিছুটা ঘটে।
ওয়ান্না সমস্ত

6

আমাদের একটি সাবক্লাস রয়েছে ThreadPoolExecutorযা একটি অতিরিক্ত creationThresholdএবং ওভাররাইড নেয় execute

public void execute(Runnable command) {
    super.execute(command);
    final int poolSize = getPoolSize();
    if (poolSize < getMaximumPoolSize()) {
        if (getQueue().size() > creationThreshold) {
            synchronized (this) {
                setCorePoolSize(poolSize + 1);
                setCorePoolSize(poolSize);
            }
        }
    }
}

হতে পারে এটি খুব সাহায্য করে, তবে আপনার অবশ্যই অবশ্যই আরও দক্ষতা দেখায় ...


মজাদার. এই জন্য ধন্যবাদ। আমি আসলে জানতাম না যে মূল আকারটি পরিবর্তনীয় ছিল।
ধূসর

এখন যেহেতু আমি এটি সম্পর্কে আরও কিছু মনে করি, সারিটির আকার চেক করার ক্ষেত্রে এই সমাধানটি আমার চেয়ে ভাল। offer(...)পদ্ধতিটি কেবল falseশর্তসাপেক্ষে ফিরে আসার জন্য আমি আমার উত্তরটি টুইট করেছি । ধন্যবাদ!
ধূসর

4

প্রস্তাবিত উত্তরটি জেডিকে থ্রেড পুলের মাধ্যমে সমস্যার একটি মাত্র (1) সমাধান করে:

  1. জেডি কে থ্রেড পুলগুলি সারিবদ্ধ হওয়ার দিকে পক্ষপাতযুক্ত are সুতরাং নতুন থ্রেড তৈরি করার পরিবর্তে তারা টাস্কটি সারি করবে। কেবল সারি যদি তার সীমাতে পৌঁছে যায় তবে থ্রেড পুলটি একটি নতুন থ্রেড উত্পন্ন করবে।

  2. থ্রেড অবসর যখন লোড লাইট হয় তখন ঘটে না। উদাহরণস্বরূপ, যদি আমাদের পুলটিতে আঘাতের কাজ হয় যা পুলটি সর্বোচ্চে চলে যায় এবং এরপরে একাধিক 2 টি কাজের হালকা লোড হয়, পুলটি থ্রেড অবসর প্রতিরোধকারী হালকা লোডটি সরবরাহ করতে সমস্ত থ্রেড ব্যবহার করবে। (কেবলমাত্র 2 টি থ্রেডের প্রয়োজন হবে ...)

উপরের আচরণে অসন্তুষ্ট হয়ে আমি এগিয়ে গিয়ে উপরের ঘাটতিগুলি কাটিয়ে উঠতে একটি পুল বাস্তবায়ন করেছি।

2 সমাধান করার জন্য) লাইফোর সময়সূচী ব্যবহার করা সমস্যার সমাধান করে। এসিএম আবেদনকারী 2015 সম্মেলনে বেন মুরার এই ধারণাটি উপস্থাপন করেছিলেন: সিস্টেমস @ ফেসবুক স্কেল

সুতরাং একটি নতুন বাস্তবায়নের জন্ম হয়েছিল:

লাইফথ্রেডপুলএক্সেকিউটার এসকিউপি

এখনও অবধি এই বাস্তবায়ন ZEL এর জন্য অ্যাসিঙ্ক এক্সিকিউশন পারফমেন্স উন্নত করে ।

বাস্তবায়ন নির্দিষ্ট ব্যবহারের ক্ষেত্রে উচ্চতর পারফরম্যান্স প্রদান করে প্রসঙ্গের সুইচ ওভারহেড হ্রাস করতে সক্ষম স্পিন।

আশা করি এটা সাহায্য করবে...

পিএস: জেডি কে ফর্ক পুলে যোগদান এক্সিকিউটর সার্ভিস বাস্তবায়ন করে এবং একটি "নরমাল" থ্রেড পুল হিসাবে কাজ করে, বাস্তবায়ন হয় পারফরম্যান্ট, এটি লাইফো থ্রেড শিডিয়ুলিং ব্যবহার করে, তবে অভ্যন্তরীণ কাতারের আকার, অবসর সময়সীমার উপর কোনও নিয়ন্ত্রণ নেই ... এবং সবচেয়ে গুরুত্বপূর্ণ বিষয়গুলি কাজগুলি হতে পারে না এগুলি বাতিল করার সময় বাধাগ্রস্ত হয়


4
খুব খারাপ যে এই প্রয়োগের এত বেশি বাহ্যিক নির্ভরতা রয়েছে। এটি আমার পক্ষে অকেজো করে দেওয়া: - /
মার্টিন এল।

4
এটি সত্যিই ভাল পয়েন্ট (2 য়)। দুর্ভাগ্যক্রমে, বাস্তবায়ন বাহ্যিক নির্ভরতা থেকে পরিষ্কার নয়, তবে আপনি চাইলে এখনও গ্রহণ করতে পারেন।
আলেক্সি ভ্লাসভ

1

দ্রষ্টব্য: আমি এখন আমার অন্য উত্তরটি পছন্দ এবং সুপারিশ করি

মিথ্যা প্রত্যাবর্তনের জন্য সারি পরিবর্তন করার মূল ধারণাটি অনুসরণ করে আমার আর একটি প্রস্তাব রয়েছে। এর একটিতে সমস্ত কাজ কাতারে প্রবেশ করতে পারে, তবে যখনই কোনও কাজ সন্নিবেশ করা হয় execute(), তখন আমরা এটি একটি প্রেরিত নো-অপ্ট টাস্কের সাথে অনুসরণ করি যা কাতার প্রত্যাখ্যান করে, একটি নতুন থ্রেড ছড়িয়ে দেবে, যা তত্ক্ষণাত নো-ওপিকে কার্যকর করবে কিউ থেকে কিছু।

যেহেতু শ্রমিকের থ্রেডগুলি LinkedBlockingQueueকোনও নতুন কাজের জন্য পোলিং করছে , কোনও থ্রেড উপলব্ধ থাকলেও এটি তৈরি করা সম্ভব। থ্রেড উপলব্ধ থাকলেও নতুন থ্রেড ছড়িয়ে দেওয়া এড়াতে, আমাদের কতটা থ্রেড কাতারে নতুন কাজের জন্য অপেক্ষা করছে এবং এটির জন্য অপেক্ষা করা থ্রেডের চেয়ে কাতারে আরও কাজ করার সময় কেবল একটি নতুন থ্রেড তৈরি করতে হবে।

final Runnable SENTINEL_NO_OP = new Runnable() { public void run() { } };

final AtomicInteger waitingThreads = new AtomicInteger(0);

BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>() {
    @Override
    public boolean offer(Runnable e) {
        // offer returning false will cause the executor to spawn a new thread
        if (e == SENTINEL_NO_OP) return size() <= waitingThreads.get();
        else return super.offer(e);
    }

    @Override
    public Runnable poll(long timeout, TimeUnit unit) throws InterruptedException {
        try {
            waitingThreads.incrementAndGet();
            return super.poll(timeout, unit);
        } finally {
            waitingThreads.decrementAndGet();
        }
    }

    @Override
    public Runnable take() throws InterruptedException {
        try {
            waitingThreads.incrementAndGet();
            return super.take();
        } finally {
            waitingThreads.decrementAndGet();
        }
    }
};

ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 50, 60, TimeUnit.SECONDS, queue) {
    @Override
    public void execute(Runnable command) {
        super.execute(command);
        if (getQueue().size() > waitingThreads.get()) super.execute(SENTINEL_NO_OP);
    }
};
threadPool.setRejectedExecutionHandler(new RejectedExecutionHandler() {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        if (r == SENTINEL_NO_OP) return;
        else throw new RejectedExecutionException();            
    }
});

0

সবচেয়ে ভাল সমাধান যা আমি ভাবতে পারি তা হ'ল প্রসারিত করা।

ThreadPoolExecutorকয়েকটি হুক পদ্ধতি সরবরাহ করে: beforeExecuteএবং afterExecute। আপনার এক্সটেনশনে আপনি টাস্কগুলিতে ফিড দেওয়ার জন্য একটি সীমাবদ্ধ সারি এবং ওভারফ্লো হ্যান্ডেল করার জন্য দ্বিতীয় সীমাহীন সারির ব্যবহার বজায় রাখতে পারেন। কেউ ফোন করলে submitআপনি অনুরোধটি সীমাবদ্ধ সারিতে রেখে দেওয়ার চেষ্টা করতে পারেন। যদি আপনার কোনও ব্যতিক্রম দেখা হয়ে যায় তবে আপনি কেবল আপনার ওভারফ্লো সারিতে কাজটি আটকে দিন। আপনি তারপর ব্যবহার করতে পারেafterExecute কোনও কাজ শেষ করে ওভারফ্লো সারিতে কিছু আছে কিনা তা দেখতে হুকটি করতে পারেন। এইভাবে, নির্বাহক প্রথমে তার সীমানাযুক্ত সারির স্টাফগুলির যত্ন নেবে এবং সময় অনুমতি অনুসারে স্বয়ংক্রিয়ভাবে এই সীমাহীন সারি থেকে টানবে।

এটি আপনার সমাধানের চেয়ে বেশি কাজ বলে মনে হচ্ছে তবে কমপক্ষে এটি কাতারে অপ্রত্যাশিত আচরণ দেওয়া জড়িত না। আমি আরও কল্পনা করেছি যে ব্যতিক্রমগুলির উপর নির্ভর করার পরিবর্তে সারি ও থ্রেডের স্থিতি পরীক্ষা করার আরও ভাল উপায় আছে, যা নিক্ষেপ করতে যথেষ্ট ধীর।


আমি এই সমাধানটি পছন্দ করি না। আমি যথেষ্ট নিশ্চিত যে থ্রেডপুলএক্সেকিউটার উত্তরাধিকারের জন্য ডিজাইন করা হয়নি।
স্কটব

জাভাডকের ডানদিকে আসলে একটি এক্সটেনশনের উদাহরণ রয়েছে। তারা জানিয়েছে যে বেশিরভাগ সম্ভবত কেবল হুক পদ্ধতিগুলি বাস্তবায়িত করবে, তবে প্রসারিত করার সময় আপনাকে আরও কী কী সন্ধান করতে হবে তা আপনাকে বলে।
বুস্টেম্পি

0

দ্রষ্টব্য: জেডি কে থ্রেডপুলঅ্যাক্সিকিউটারের জন্য যখন আপনার সীমাবদ্ধ সারি রয়েছে, আপনি যখন প্রস্তাবটি মিথ্যা ফিরিয়ে দিচ্ছেন কেবল তখনই নতুন থ্রেড তৈরি করছেন। আপনি কলার রুনস পলিসির সাথে দরকারী কিছু পেতে পারেন যা কিছুটা ব্যাকপ্রেসার তৈরি করে এবং সরাসরি কলার থ্রেডে কল করে।

পুলের তৈরি থ্রেডগুলি থেকে আমার সম্পাদন করার জন্য আমার কাজগুলি প্রয়োজন এবং সময়সূচীটির জন্য একটি চৌম্বক সারি থাকতে হবে, যখন পুলের মধ্যে থ্রেডের সংখ্যা কোরপুল সাইজ এবং সর্বোচ্চপুলসাইজের মধ্যে বাড়তে বা সঙ্কুচিত হতে পারে ...

আমি থ্রেডপুলএক্সিকিউটার থেকে সম্পূর্ণ অনুলিপি পেস্ট করে শেষ করেছি এবং এক্সিকিউট পদ্ধতিতে কিছুটা পরিবর্তন করেছি কারণ দুর্ভাগ্যক্রমে এটি এক্সটেনশন দ্বারা করা সম্ভব হয়নি (এটি ব্যক্তিগত পদ্ধতিগুলি কল করে)।

নতুন অনুরোধ উপস্থিত হওয়ার সাথে সাথে নতুন থ্রেডগুলি ছড়িয়ে দিতে চাইনি এবং সমস্ত থ্রেড ব্যস্ত থাকে (কারণ আমার সাধারণ স্বল্পকালীন কাজ রয়েছে)। আমি একটি প্রান্তিক জোড় যুক্ত করেছি তবে এটিকে আপনার প্রয়োজনে নির্দ্বিধায় অনুভব করতে পারি (সম্ভবত বেশিরভাগ ক্ষেত্রে আইও এই প্রান্তিকতাটি সরিয়ে দেওয়া ভাল)

private final AtomicInteger activeWorkers = new AtomicInteger(0);
private volatile double threshold = 0.7d;

protected void beforeExecute(Thread t, Runnable r) {
    activeWorkers.incrementAndGet();
}
protected void afterExecute(Runnable r, Throwable t) {
    activeWorkers.decrementAndGet();
}
public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();

        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }

        if (isRunning(c) && this.workQueue.offer(command)) {
            int recheck = this.ctl.get();
            if (!isRunning(recheck) && this.remove(command)) {
                this.reject(command);
            } else if (workerCountOf(recheck) == 0) {
                this.addWorker((Runnable) null, false);
            }
            //>>change start
            else if (workerCountOf(recheck) < maximumPoolSize //
                && (activeWorkers.get() > workerCountOf(recheck) * threshold
                    || workQueue.size() > workerCountOf(recheck) * threshold)) {
                this.addWorker((Runnable) null, false);
            }
            //<<change end
        } else if (!this.addWorker(command, false)) {
            this.reject(command);
        }
    }
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.