লুপ মুদ্রণ বিবৃতি ছাড়া অন্য থ্রেড দ্বারা পরিবর্তিত মান দেখতে পাবে না


91

আমার কোডে আমার কাছে একটি লুপ রয়েছে যা কিছু থিম থেকে আলাদা থ্রেড থেকে পরিবর্তিত হওয়ার জন্য অপেক্ষা করে। অন্য থ্রেডটি কাজ করে, তবে আমার লুপটি পরিবর্তিত মানটি কখনই দেখতে পায় না। এটি চিরকাল অপেক্ষা করে। যাইহোক, যখন আমি System.out.printlnলুপে একটি বিবৃতি রাখি , এটি হঠাৎ কাজ করে! কেন?


নিম্নলিখিতটি আমার কোডের একটি উদাহরণ:

class MyHouse {
    boolean pizzaArrived = false;

    void eatPizza() {
        while (pizzaArrived == false) {
            //System.out.println("waiting");
        }

        System.out.println("That was delicious!");
    }

    void deliverPizza() {
        pizzaArrived = true;
    }
}

লুপটি চলার সময়, ভেরিয়েবলটি deliverPizza()সেট করতে আমি একটি আলাদা থ্রেড থেকে কল করি pizzaArrived। লুপটি কেবল তখনই কাজ করে যখন আমি System.out.println("waiting");বিবৃতিটিকে অসন্তুষ্ট করি । কি হচ্ছে?

উত্তর:


152

JVM ধরে নিতে অনুমতি দেওয়া হয়েছে যে অন্যান্য থ্রেডগুলি pizzaArrivedলুপের সময় পরিবর্তনশীল পরিবর্তন করে না । অন্য কথায়, এটি pizzaArrived == falseলুপের বাইরে পরীক্ষা উত্তোলন করতে পারে, এটি সর্বোত্তম করে:

while (pizzaArrived == false) {}

এটিতে:

if (pizzaArrived == false) while (true) {}

যা একটি অসীম লুপ।

এক থ্রেডের করা পরিবর্তনগুলি অন্য থ্রেডগুলিতে দৃশ্যমান তা নিশ্চিত করার জন্য আপনাকে অবশ্যই সর্বদা থ্রেডগুলির মধ্যে কিছুটা সিঙ্ক্রোনাইজেশন যুক্ত করতে হবে । এটি করার সহজ উপায় হ'ল ভাগ করা পরিবর্তনশীল করা volatile:

volatile boolean pizzaArrived = false;

পরিবর্তনশীল করা volatileগ্যারান্টি দেয় যে বিভিন্ন থ্রেড এতে একে অপরের পরিবর্তনের প্রভাব দেখতে পাবে। এটি JVM কে pizzaArrivedলুপের বাইরে পরীক্ষাটি মূল্য বা উত্তোলন করতে বাধা দেয় । পরিবর্তে, এটি অবশ্যই প্রতিবার আসল ভেরিয়েবলের মান পড়তে হবে।

(আরও আনুষ্ঠানিকভাবে, ভেরিয়েবলের অ্যাক্সেসের মধ্যে volatileএকটি সংঘর্ষের আগে সম্পর্ক তৈরি হয় This এর অর্থ পিৎজার সরবরাহের আগে থ্রেডের অন্যান্য কাজগুলি পিৎজা প্রাপ্ত থ্রেডেও দৃশ্যমান হয়, এমনকি যদি এই অন্যান্য পরিবর্তনগুলি volatileভেরিয়েবলের নাও হয় ))

সিঙ্ক্রোনাইজড পদ্ধতিগুলি মূলত পারস্পরিক বর্জন বাস্তবায়নের জন্য ব্যবহৃত হয় (একই সাথে দুটি জিনিস ঘটতে বাধা দেয়), তবে তাদের সমস্ত একই পার্শ্ব-প্রতিক্রিয়াও volatileরয়েছে। পরিবর্তনগুলি পড়তে এবং লেখার সময় সেগুলি ব্যবহার করা অন্য থ্রেডগুলিতে পরিবর্তনগুলি দৃশ্যমান করার আরও একটি উপায়:

class MyHouse {
    boolean pizzaArrived = false;

    void eatPizza() {
        while (getPizzaArrived() == false) {}
        System.out.println("That was delicious!");
    }

    synchronized boolean getPizzaArrived() {
        return pizzaArrived;
    }

    synchronized void deliverPizza() {
        pizzaArrived = true;
    }
}

একটি মুদ্রণ বিবৃতি প্রভাব

System.outএকটি PrintStreamবস্তু। এর পদ্ধতিগুলি PrintStreamএইভাবে সিঙ্ক্রোনাইজ করা হয়:

public void println(String x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}

সিঙ্ক্রোনাইজেশন pizzaArrivedলুপ চলাকালীন ক্যাশে হওয়া প্রতিরোধ করে । দৃrict়ভাবে বলতে গেলে, উভয় থ্রেড অবশ্যই ভ্যারিয়েবলের পরিবর্তনগুলি দৃশ্যমান তা গ্যারান্টি হিসাবে একই অবজেক্টে সিঙ্ক্রোনাইজ করতে হবে। (উদাহরণস্বরূপ, printlnসেট করার পরে কল করা pizzaArrivedএবং পড়ার আগে আবার ফোন pizzaArrivedকরা সঠিক হবে)) যদি কেবল একটি থ্রেড নির্দিষ্ট কোনও বস্তুর সাথে সিঙ্ক্রোনাইজ করে তবে জেভিএম এটিকে এড়িয়ে যাওয়ার অনুমতি দেয়। অনুশীলনে, জেভিএম প্রমাণ করার মতো যথেষ্ট স্মার্ট নয় যে printlnসেটিংসের পরে অন্য থ্রেডগুলি কল করবে না pizzaArrived, সুতরাং এটি ধরে নিয়েছে যে তারা পারে। অতএব, আপনি কল করলে লুপের সময় এটি পরিবর্তনশীলটিকে ক্যাশে করতে পারে না System.out.println। এজন্য লুপগুলি এই কাজটি পছন্দ করে যখন তাদের একটি মুদ্রণ বিবৃতি রয়েছে, যদিও এটি সঠিক ঠিক নয়।

System.outএই প্রভাব তৈরি করার একমাত্র উপায় ব্যবহার নয়, তবে এটিই সেই ব্যক্তিদের মধ্যে প্রায়শই আবিষ্কার হয় যখন তারা যখন ডিবাগ করার চেষ্টা করছেন তখন কেন তাদের লুপটি কাজ করে না!


আরও বড় সমস্যা

while (pizzaArrived == false) {}একটি ব্যস্ত-অপেক্ষা লুপ হয়। এটা খারাপ! এটি অপেক্ষা করার সময়, এটি সিপিইউকে হোগ করে, যা অন্যান্য অ্যাপ্লিকেশনকে ধীর করে দেয় এবং সিস্টেমটির পাওয়ার ব্যবহার, তাপমাত্রা এবং পাখার গতি বাড়িয়ে তোলে। আদর্শভাবে, আমরা লুপ থ্রেডটি অপেক্ষা করতে করতে ঘুমাতে চাই, সুতরাং এটি সিপিইউতে জড়িত না।

এটি করার কিছু উপায় এখানে রয়েছে:

অপেক্ষা / অবহিত ব্যবহার

একটি নিম্ন স্তরের সমাধানটি অপেক্ষা / অবহিত পদ্ধতিগুলি ব্যবহার করেObject :

class MyHouse {
    boolean pizzaArrived = false;

    void eatPizza() {
        synchronized (this) {
            while (!pizzaArrived) {
                try {
                    this.wait();
                } catch (InterruptedException e) {}
            }
        }

        System.out.println("That was delicious!");
    }

    void deliverPizza() {
        synchronized (this) {
            pizzaArrived = true;
            this.notifyAll();
        }
    }
}

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

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

ব্লকিংকুই

BlockingQueueউত্পাদক-ভোক্তা সারি প্রয়োগ করতে ব্যবহৃত হয়। "ভোক্তারা" কাতারের সামনের অংশ থেকে আইটেম নিয়ে যায় এবং "উত্পাদক" আইটেমগুলি পিছনে চাপ দেয়। একটি উদাহরণ:

class MyHouse {
    final BlockingQueue<Object> queue = new LinkedBlockingQueue<>();

    void eatFood() throws InterruptedException {
        // take next item from the queue (sleeps while waiting)
        Object food = queue.take();
        // and do something with it
        System.out.println("Eating: " + food);
    }

    void deliverPizza() throws InterruptedException {
        // in producer threads, we push items on to the queue.
        // if there is space in the queue we can return immediately;
        // the consumer thread(s) will get to it later
        queue.put("A delicious pizza");
    }
}

দ্রষ্টব্য: গুলি putএবং ছুঁড়ে মারার takeপদ্ধতিগুলি হ'ল হ্যান্ডেল করা আবশ্যক ব্যতিক্রমগুলি যাচাই করা হয়। উপরের কোডে, সরলতার জন্য, ব্যতিক্রমগুলি পুনর্বিবেচিত হয়। আপনি পদ্ধতিগুলিতে ব্যতিক্রমগুলি ধরতে পছন্দ করতে পারেন এবং এটি সফল হয়েছে তা নিশ্চিত হওয়ার জন্য পুনরায় চেষ্টা করুন বা কলটি কল করুন। কদর্যতার এক বিন্দু ছাড়াও ব্যবহার করা খুব সহজ।BlockingQueueInterruptedExceptionBlockingQueue

এখানে অন্য কোনও সিঙ্ক্রোনাইজেশন প্রয়োজন নেই কারণ BlockingQueueএটি নিশ্চিত করে যে কাতারে আইটেমগুলি রাখার আগে সমস্ত থ্রেড সেই আইটেমগুলি বাইরে নিয়ে যাওয়া থ্রেডগুলিতে দৃশ্যমান।

এক্সিকিউটররা

Executorগুলি তৈরির মতো BlockingQueueযা কার্য সম্পাদন করে। উদাহরণ:

// A "SingleThreadExecutor" has one work thread and an unlimited queue
ExecutorService executor = Executors.newSingleThreadExecutor();

Runnable eatPizza = () -> { System.out.println("Eating a delicious pizza"); };
Runnable cleanUp = () -> { System.out.println("Cleaning up the house"); };

// we submit tasks which will be executed on the work thread
executor.execute(eatPizza);
executor.execute(cleanUp);
// we continue immediately without needing to wait for the tasks to finish

বিস্তারিত জানার জন্য ডক দেখুন Executor, ExecutorServiceএবং Executors

ইভেন্ট হ্যান্ডলিং

ইউআই-তে কোনও কিছুর জন্য ব্যবহারকারী অপেক্ষা করার সময় লুপ করা ভুল। পরিবর্তে, ইউআই টুলকিটের ইভেন্ট হ্যান্ডলিং বৈশিষ্ট্যগুলি ব্যবহার করুন। দোলে , উদাহরণস্বরূপ:

JLabel label = new JLabel();
JButton button = new JButton("Click me");
button.addActionListener((ActionEvent e) -> {
    // This event listener is run when the button is clicked.
    // We don't need to loop while waiting.
    label.setText("Button was clicked");
});

কারণ ইভেন্ট হ্যান্ডলার ইভেন্ট প্রেরণের থ্রেডে চালিত হয়, ইভেন্ট হ্যান্ডলারটিতে দীর্ঘ কাজ করা কাজ শেষ না হওয়া অবধি ইউআইয়ের সাথে অন্য মিথস্ক্রিয়াকে অবরুদ্ধ করে। ধীরে ধীরে অপারেশনগুলি একটি নতুন থ্রেডে শুরু করা যেতে পারে, বা উপরের কৌশলগুলির (ওয়েট / নোটিফিকেশন, এ BlockingQueue, বা Executor) ব্যবহার করে ওয়েটিং থ্রেডে প্রেরণ করা যেতে পারে । আপনি এমন কোনও ব্যবহার করতে পারেন SwingWorker, যা এর জন্য ঠিক নকশা করা হয়েছে এবং স্বয়ংক্রিয়ভাবে একটি পটভূমি কর্মীর থ্রেড সরবরাহ করে:

JLabel label = new JLabel();
JButton button = new JButton("Calculate answer");

// Add a click listener for the button
button.addActionListener((ActionEvent e) -> {

    // Defines MyWorker as a SwingWorker whose result type is String:
    class MyWorker extends SwingWorker<String,Void> {
        @Override
        public String doInBackground() throws Exception {
            // This method is called on a background thread.
            // You can do long work here without blocking the UI.
            // This is just an example:
            Thread.sleep(5000);
            return "Answer is 42";
        }

        @Override
        protected void done() {
            // This method is called on the Swing thread once the work is done
            String result;
            try {
                result = get();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            label.setText(result); // will display "Answer is 42"
        }
    }

    // Start the worker
    new MyWorker().execute();
});

টাইমারস

পর্যায়ক্রমিক ক্রিয়া সম্পাদন করতে, আপনি একটি ব্যবহার করতে পারেন java.util.Timer। আপনার নিজের টাইমিং লুপটি লেখার চেয়ে ব্যবহার করা সহজ এবং শুরু এবং থামানো আরও সহজ। এই ডেমোটি প্রতি সেকেন্ডে বর্তমান সময়টি মুদ্রণ করে:

Timer timer = new Timer();
TimerTask task = new TimerTask() {
    @Override
    public void run() {
        System.out.println(System.currentTimeMillis());
    }
};
timer.scheduleAtFixedRate(task, 0, 1000);

প্রত্যেকের java.util.Timerনিজস্ব ব্যাকগ্রাউন্ড থ্রেড রয়েছে যা এর নির্ধারিত TimerTaskগুলি চালানোর জন্য ব্যবহৃত হয় । স্বাভাবিকভাবেই, থ্রেডটি কার্যগুলির মধ্যে ঘুমায়, তাই এটি সিপিইউকে জড়িত করে না।

সুইং কোডে, একটিও রয়েছে javax.swing.Timerযা একই রকম, তবে এটি শ্রোতাদের সুইং থ্রেডে চালিত করে, তাই আপনি নিজেই থ্রেডগুলি স্যুইচ না করে সুইং উপাদানগুলির সাথে নিরাপদে যোগাযোগ করতে পারেন:

JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Timer timer = new Timer(1000, (ActionEvent e) -> {
    frame.setTitle(String.valueOf(System.currentTimeMillis()));
});
timer.setRepeats(true);
timer.start();
frame.setVisible(true);

অন্যান্য উপায়

আপনি যদি মাল্টিথ্রেডেড কোড লিখছেন তবে কী পাওয়া যায় তা দেখার জন্য এই প্যাকেজগুলির ক্লাসগুলি অন্বেষণ করার জন্য মূল্যবান:

এবং জাভা টিউটোরিয়ালগুলির কনকুরન્સી বিভাগটি দেখুন । মাল্টিথ্রেডিং জটিল, তবে প্রচুর সাহায্য পাওয়া যায়!


খুব পেশাদার উত্তর, এটি পড়ার পরে আমার মনে কোনও ভুল ধারণা
রইল

4
দুর্দান্ত উত্তর। আমি জাভা থ্রেডের সাথে বেশ কিছুক্ষণ কাজ করছি এবং এখনও এখানে কিছু শিখেছি ( wait()সিঙ্ক্রোনাইজেশন লক প্রকাশ করে!)।
ব্রিম্বরিয়াম

ধন্যবাদ, বোয়ান! দুর্দান্ত উত্তর, এটি উদাহরণ সহ পুরো নিবন্ধের মতো! হ্যাঁ, পছন্দ হয়েছে "অপেক্ষা করুন (সিঙ্ক্রোনাইজেশন লক প্রকাশ করে")
ক্যারিল ইভানাউ

java public class ThreadTest { private static boolean flag = false; private static class Reader extends Thread { @Override public void run() { while(flag == false) {} System.out.println(flag); } } public static void main(String[] args) { new Reader().start(); flag = true; } } @ বনান, এই কোডটি pizzaArrived == falseলুপের বাইরে পরীক্ষা উত্তোলন করে না , এবং লুপটি মূল থ্রেড দ্বারা পরিবর্তিত পতাকা দেখতে পারে, কেন?
গাউসক্লাব

4
@gaussclb আপনার যদি বোঝানো হয় যে আপনি কোনও শ্রেণি ফাইলটি নিষ্পত্তি করেছেন, ঠিক correct জাভা সংকলক প্রায় কোনও অপ্টিমাইজেশন করে না। উত্তোলন জেভিএম করেছে by আপনাকে নেটিভ মেশিন কোডটি বিযুক্ত করতে হবে। চেষ্টা করুন: wiki.openjdk.java.net/display/HotSpot/ মুদ্রণবিহীনতা
Boann
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.