থ্রেড কিভাবে ইন্টারঅ্যাক্ট করে তার বিশদ বিবরণের একটি সংক্ষিপ্ত বিবরণ। পূর্বে, আমরা দেখেছি কিভাবে থ্রেডগুলি একে অপরের সাথে সিঙ্ক্রোনাইজ করা হয়। এইবার আমরা থ্রেডগুলি ইন্টারঅ্যাক্ট করার সময় যে সমস্যাগুলি উদ্ভূত হতে পারে সেগুলির মধ্যে ডুব দেব এবং কীভাবে সেগুলি এড়ানো যায় সে সম্পর্কে আমরা কথা বলব৷ আরও গভীরভাবে অধ্যয়নের জন্য আমরা কিছু দরকারী লিঙ্কও প্রদান করব।
একটি JVisualVM প্লাগইন ইনস্টল করার সাথে (Tools -> Plugins এর মাধ্যমে), আমরা দেখতে পারি কোথায় অচলাবস্থা ঘটেছে:
JVisualVM-এর মতে, আমরা ঘুমের সময়কাল এবং পার্কের সময়কাল দেখতে পাই (এটি হল যখন একটি থ্রেড একটি লক অর্জন করার চেষ্টা করে — এটি পার্কের অবস্থায় প্রবেশ করে, যেমন আমরা আগে আলোচনা করেছি যখন আমরা থ্রেড সিঙ্ক্রোনাইজেশন সম্পর্কে কথা বলেছিলাম ) । আপনি এখানে লাইভলকের একটি উদাহরণ দেখতে পারেন: Java - Thread Livelock ।
আপনি এখানে একটি দুর্দান্ত উদাহরণ দেখতে পারেন: Java - Thread Starvation and Fairness । এই উদাহরণটি দেখায় যে অনাহারে থ্রেডের সাথে কী ঘটে এবং কীভাবে একটি ছোট পরিবর্তন আপনাকে লোড সমানভাবে বিতরণ করতে দেয়

ভূমিকা
সুতরাং, আমরা জানি যে জাভা থ্রেড আছে. আপনি এটি সম্পর্কে আরও ভাল একসাথে শিরোনামের পর্যালোচনাতে পড়তে পারেন : জাভা এবং থ্রেড ক্লাস। পার্ট I — থ্রেডস অফ এক্সিকিউশন । এবং আমরা বেটার একসাথে: জাভা এবং থ্রেড ক্লাস শিরোনামের পর্যালোচনাতে যে থ্রেডগুলি একে অপরের সাথে সিঙ্ক্রোনাইজ করতে পারে তা অনুসন্ধান করেছি । পার্ট II — সিঙ্ক্রোনাইজেশন । থ্রেডগুলি একে অপরের সাথে কীভাবে যোগাযোগ করে সে সম্পর্কে কথা বলার সময়। কিভাবে তারা ভাগ করা সম্পদ ভাগ করে? এখানে কি সমস্যা হতে পারে?
অচলাবস্থা
সব থেকে ভীতিকর সমস্যা হল অচলাবস্থা। অচলাবস্থা হল যখন দুই বা ততোধিক থ্রেড অনন্তকাল অন্যটির জন্য অপেক্ষা করছে। আমরা ওরাকল ওয়েবপৃষ্ঠা থেকে একটি উদাহরণ নেব যা অচলাবস্থা বর্ণনা করে :
public class Deadlock {
static class Friend {
private final String name;
public Friend(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public synchronized void bow(Friend bower) {
System.out.format("%s: %s bowed to me!%n",
this.name, bower.getName());
bower.bowBack(this);
}
public synchronized void bowBack(Friend bower) {
System.out.format("%s: %s bowed back to me!%n",
this.name, bower.getName());
}
}
public static void main(String[] args) {
final Friend alphonse = new Friend("Alphonse");
final Friend gaston = new Friend("Gaston");
new Thread(() -> alphonse.bow(gaston)).start();
new Thread(() -> gaston.bow(alphonse)).start();
}
}
এখানে প্রথমবার ডেডলক নাও ঘটতে পারে, কিন্তু যদি আপনার প্রোগ্রামটি হ্যাং হয়ে যায়, তবে এটি চালানোর সময় jvisualvm
: 
"Thread-1" - Thread t@12
java.lang.Thread.State: BLOCKED
at Deadlock$Friend.bowBack(Deadlock.java:16)
- waiting to lock <33a78231> (a Deadlock$Friend) owned by "Thread-0" t@11
থ্রেড 1 থ্রেড 0 থেকে লকের জন্য অপেক্ষা করছে। কেন এমন হয়? Thread-1
চলমান শুরু করে এবং Friend#bow
পদ্ধতিটি কার্যকর করে। এটি কীওয়ার্ড দিয়ে চিহ্নিত করা হয়েছে , যার মানে আমরা (বর্তমান বস্তুর) synchronized
জন্য মনিটরটি অর্জন করছি । this
পদ্ধতির ইনপুট অন্য Friend
বস্তুর একটি রেফারেন্স ছিল. এখন, Thread-1
অন্য দিকে পদ্ধতিটি চালাতে চায় Friend
, এবং এটি করার জন্য অবশ্যই এর লকটি অর্জন করতে হবে। কিন্তু যদি অন্য থ্রেডটি (এই ক্ষেত্রে Thread-0
) পদ্ধতিতে প্রবেশ করতে সক্ষম হয় bow()
, তাহলে লকটি ইতিমধ্যেই অর্জিত হয়েছে এবং Thread-1
অপেক্ষা করছেThread-0
, এবং বিপরীতভাবে. এই অচলাবস্থা অমীমাংসিত, এবং আমরা একে অচলাবস্থা বলি। একটি মৃত্যুর খপ্পরের মতো যা মুক্তি দেওয়া যায় না, অচলাবস্থা হল পারস্পরিক অবরোধ যা ভাঙা যায় না। অচলাবস্থার আরেকটি ব্যাখ্যার জন্য, আপনি এই ভিডিওটি দেখতে পারেন: ডেডলক এবং লাইভলক ব্যাখ্যা করা হয়েছে ।
লাইভলক
অচলাবস্থা থাকলে, লাইভলকও আছে কি? হ্যাঁ, আছে :) লাইভলক ঘটে যখন থ্রেডগুলি বাহ্যিকভাবে জীবিত বলে মনে হয়, কিন্তু তারা কিছুই করতে অক্ষম, কারণ তাদের কাজ চালিয়ে যাওয়ার জন্য তাদের জন্য প্রয়োজনীয় শর্ত(গুলি) পূরণ করা যায় না। মূলত, লাইভলক অচলাবস্থার মতো, তবে থ্রেডগুলি মনিটরের জন্য অপেক্ষা করে "হ্যাং" করে না। পরিবর্তে, তারা চিরতরে কিছু করছে। উদাহরণ স্বরূপ:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class App {
public static final String ANSI_BLUE = "\u001B[34m";
public static final String ANSI_PURPLE = "\u001B[35m";
public static void log(String text) {
String name = Thread.currentThread().getName(); // Like "Thread-1" or "Thread-0"
String color = ANSI_BLUE;
int val = Integer.valueOf(name.substring(name.lastIndexOf("-") + 1)) + 1;
if (val != 0) {
color = ANSI_PURPLE;
}
System.out.println(color + name + ": " + text + color);
try {
System.out.println(color + name + ": wait for " + val + " sec" + color);
Thread.currentThread().sleep(val * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Lock first = new ReentrantLock();
Lock second = new ReentrantLock();
Runnable locker = () -> {
boolean firstLocked = false;
boolean secondLocked = false;
try {
while (!firstLocked || !secondLocked) {
firstLocked = first.tryLock(100, TimeUnit.MILLISECONDS);
log("First Locked: " + firstLocked);
secondLocked = second.tryLock(100, TimeUnit.MILLISECONDS);
log("Second Locked: " + secondLocked);
}
first.unlock();
second.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(locker).start();
new Thread(locker).start();
}
}
এই কোডের সাফল্য নির্ভর করে জাভা থ্রেড শিডিয়ুলার যে ক্রমে থ্রেডগুলি শুরু করে তার উপর। যদি Thead-1
প্রথমে শুরু হয়, তাহলে আমরা লাইভলক পাই:
Thread-1: First Locked: true
Thread-1: wait for 2 sec
Thread-0: First Locked: false
Thread-0: wait for 1 sec
Thread-0: Second Locked: true
Thread-0: wait for 1 sec
Thread-1: Second Locked: false
Thread-1: wait for 2 sec
Thread-0: First Locked: false
Thread-0: wait for 1 sec
...
আপনি উদাহরণ থেকে দেখতে পারেন, উভয় থ্রেড পালাক্রমে উভয় লক অর্জন করার চেষ্টা করে, কিন্তু তারা ব্যর্থ হয়। তবে তারা অচলাবস্থায় নেই। বাহ্যিকভাবে, সবকিছু ঠিক আছে এবং তারা তাদের কাজ করছে। 
অনাহার
অচলাবস্থা এবং লাইভলক ছাড়াও, মাল্টিথ্রেডিংয়ের সময় আরেকটি সমস্যা হতে পারে: অনাহার। এই ঘটনাটি ব্লক করার আগের ফর্মগুলির থেকে আলাদা যে থ্রেডগুলি ব্লক করা হয় না — তাদের কাছে পর্যাপ্ত সংস্থান নেই। ফলস্বরূপ, যখন কিছু থ্রেড সমস্ত কার্যকর করার সময় নেয়, অন্যরা চালাতে অক্ষম হয়:
https://www.logicbig.com/
Thread.sleep()
।Thread.wait()

রেসের শর্ত
মাল্টিথ্রেডিং-এ, একটি "জাতি শর্ত" হিসাবে একটি জিনিস আছে। এই ঘটনাটি ঘটে যখন থ্রেডগুলি একটি সংস্থান ভাগ করে, কিন্তু কোডটি এমনভাবে লেখা হয় যাতে এটি সঠিক ভাগাভাগি নিশ্চিত করে না। একটি উদাহরণ দেখুন:
public class App {
public static int value = 0;
public static void main(String[] args) {
Runnable task = () -> {
for (int i = 0; i < 10000; i++) {
int oldValue = value;
int newValue = ++value;
if (oldValue + 1 != newValue) {
throw new IllegalStateException(oldValue + " + 1 = " + newValue);
}
}
};
new Thread(task).start();
new Thread(task).start();
new Thread(task).start();
}
}
এই কোডটি প্রথমবার একটি ত্রুটি তৈরি নাও করতে পারে। যখন এটি হয়, এটি এইরকম দেখতে পারে:
Exception in thread "Thread-1" java.lang.IllegalStateException: 7899 + 1 = 7901
at App.lambda$main$0(App.java:13)
at java.lang.Thread.run(Thread.java:745)
আপনি দেখতে পাচ্ছেন, newValue
একটি মান নির্ধারণ করার সময় কিছু ভুল হয়েছে। newValue
খুব বড় value
রেসের অবস্থার কারণে, একটি থ্রেড দুটি স্টেটমেন্টের মধ্যে ভেরিয়েবলের পরিবর্তন করতে পেরেছে । দেখা যাচ্ছে যে থ্রেডগুলির মধ্যে একটি দৌড় রয়েছে। এখন মনে করুন আর্থিক লেনদেনের ক্ষেত্রে অনুরূপ ভুল না করা কতটা গুরুত্বপূর্ণ... উদাহরণ এবং চিত্রগুলিও এখানে দেখা যেতে পারে: জাভা থ্রেডে রেসের অবস্থা অনুকরণ করার জন্য কোড ।
উদ্বায়ী
থ্রেডের মিথস্ক্রিয়া সম্পর্কে বলতে গেলে,volatile
কীওয়ার্ডটি উল্লেখ করার মতো। আসুন একটি সাধারণ উদাহরণ দেখি:
public class App {
public static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
Runnable whileFlagFalse = () -> {
while(!flag) {
}
System.out.println("Flag is now TRUE");
};
new Thread(whileFlagFalse).start();
Thread.sleep(1000);
flag = true;
}
}
সবচেয়ে মজার, এটি কাজ না করার সম্ভাবনা বেশি। নতুন থ্রেড ক্ষেত্রের পরিবর্তন দেখতে পাবে না flag
। ক্ষেত্রের জন্য এটি ঠিক করতে flag
, আমাদের কীওয়ার্ড ব্যবহার করতে হবে volatile
। কীভাবে এবং কেন? প্রসেসর সমস্ত ক্রিয়া সম্পাদন করে। কিন্তু গণনার ফলাফল কোথাও সংরক্ষণ করতে হবে। এই জন্য, প্রধান মেমরি আছে এবং প্রসেসর এর ক্যাশে আছে. একটি প্রসেসরের ক্যাশে মেমরির একটি ছোট অংশের মতো যা প্রধান মেমরি অ্যাক্সেস করার সময় ডেটা অ্যাক্সেস করার জন্য বেশি দ্রুত ব্যবহার করা হয়। কিন্তু সবকিছুরই একটি নেতিবাচক দিক রয়েছে: ক্যাশে ডেটা আপ-টু-ডেট নাও হতে পারে (উপরের উদাহরণে, যখন পতাকা ক্ষেত্রের মান আপডেট করা হয়নি)। তাহলেvolatile
কীওয়ার্ড JVM কে বলে যে আমরা আমাদের ভেরিয়েবল ক্যাশে করতে চাই না। এটি সমস্ত থ্রেডে আপ-টু-ডেট ফলাফল দেখতে দেয়। এটি একটি অত্যন্ত সরলীকৃত ব্যাখ্যা। কীওয়ার্ডের জন্য , আমি আপনাকে এই নিবন্ধটিvolatile
পড়ার সুপারিশ করছি । আরও তথ্যের জন্য, আমি আপনাকে জাভা মেমরি মডেল এবং জাভা ভোলাটাইল কীওয়ার্ড পড়ার পরামর্শ দিচ্ছি । উপরন্তু, এটা মনে রাখা গুরুত্বপূর্ণ যে এটি দৃশ্যমানতা সম্পর্কে, এবং পরিবর্তনের পরমাণু সম্পর্কে নয়। "রেস কন্ডিশন" বিভাগে কোডটি দেখলে, আমরা IntelliJ IDEA-তে একটি টুলটিপ দেখতে পাব: এই পরিদর্শনটি ইস্যু IDEA-61117 এর অংশ হিসাবে IntelliJ IDEA-তে যোগ করা হয়েছিল , যা 2010 সালে রিলিজ নোটে তালিকাভুক্ত করা হয়েছিল ।volatile

পরমাণু
পারমাণবিক অপারেশনগুলি এমন ক্রিয়াকলাপ যা ভাগ করা যায় না। উদাহরণস্বরূপ, একটি ভেরিয়েবলের একটি মান নির্ধারণের অপারেশনটি পারমাণবিক হতে হবে। দুর্ভাগ্যবশত, ইনক্রিমেন্ট অপারেশনটি পারমাণবিক নয়, কারণ বৃদ্ধির জন্য তিনটি CPU অপারেশন প্রয়োজন: পুরানো মান পান, এটিতে একটি যোগ করুন, তারপর মান সংরক্ষণ করুন। কেন পরমাণু গুরুত্বপূর্ণ? ইনক্রিমেন্ট অপারেশনের সাথে, যদি রেসের শর্ত থাকে, তাহলে ভাগ করা সম্পদ (অর্থাৎ ভাগ করা মান) হঠাৎ করে যে কোনো সময় পরিবর্তিত হতে পারে। অতিরিক্তভাবে, 64-বিট কাঠামো জড়িত অপারেশন, উদাহরণস্বরূপlong
এবং double
, পারমাণবিক নয়। আরও বিশদ এখানে পড়তে পারেন: 64-বিট মান পড়ার এবং লেখার সময় পরমাণু নিশ্চিত করুন । পারমাণবিকতার সাথে সম্পর্কিত সমস্যাগুলি এই উদাহরণে দেখা যেতে পারে:
public class App {
public static int value = 0;
public static AtomicInteger atomic = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
for (int i = 0; i < 10000; i++) {
value++;
atomic.incrementAndGet();
}
};
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
Thread.sleep(300);
System.out.println(value);
System.out.println(atomic.get());
}
}
বিশেষ AtomicInteger
শ্রেণী সবসময় আমাদের 30,000 দেবে, কিন্তু value
সময়ে সময়ে পরিবর্তন হবে। এই বিষয়ের একটি সংক্ষিপ্ত বিবরণ রয়েছে: জাভাতে পারমাণবিক ভেরিয়েবলের ভূমিকা । "তুলনা এবং অদলবদল" অ্যালগরিদম পারমাণবিক শ্রেণীর কেন্দ্রে অবস্থিত। আপনি এখানে লক-মুক্ত অ্যালগরিদমের তুলনা - CAS এবং FAA JDK 7 এবং 8-এর উদাহরণে বা উইকিপিডিয়ার তুলনা-এন্ড-অদলবদল নিবন্ধে এটি সম্পর্কে আরও পড়তে পারেন।
http://jeremymanson.blogspot.com/2008/11/what-volatile-means-in-java.html
ঘটে-আগে
"আগে ঘটে" নামে একটি আকর্ষণীয় এবং রহস্যময় ধারণা রয়েছে। আপনার থ্রেড অধ্যয়নের অংশ হিসাবে, আপনি এটি সম্পর্কে পড়া উচিত. ঘটছে-আগে সম্পর্ক থ্রেডের মধ্যে ক্রিয়া দেখা হবে এমন ক্রম দেখায়। অনেক ব্যাখ্যা ও ভাষ্য আছে। এখানে এই বিষয়ে সাম্প্রতিকতম উপস্থাপনাগুলির মধ্যে একটি রয়েছে: জাভা "হবে-আগে" সম্পর্ক ।সারসংক্ষেপ
এই পর্যালোচনাতে, আমরা থ্রেডগুলি কীভাবে ইন্টারঅ্যাক্ট করে তার কিছু সুনির্দিষ্ট বিষয়ে অনুসন্ধান করেছি। আমরা যে সমস্যাগুলি দেখা দিতে পারে, সেইসাথে সেগুলি সনাক্ত এবং নির্মূল করার উপায়গুলি নিয়ে আলোচনা করেছি৷ বিষয়ে অতিরিক্ত উপকরণের তালিকা:- ডবল চেক লকিং
- JSR 133 (জাভা মেমরি মডেল) FAQ
- IQ 35: কিভাবে একটি অচলাবস্থা প্রতিরোধ করতে?
- ডগলাস হকিন্স (2017) দ্বারা জাভাতে সমসাময়িক ধারণা
GO TO FULL VERSION