
Javaのスレッドと並行処理は、効率的なプログラム開発に欠かせません。これまでは初心者向けの話でしたが、スレッドを深く理解するには、もう一歩踏み込む必要があります。
この記事ではスレッドの本質と「並行処理」の実態を学び、本当に効率的なスレッド設計 について解説します。
Javaの基礎知識 🔵 Java の基礎知識(基礎編) 🔵 Java の基礎知識(実践編)
📌 文法とルールをしっかり習得。実務の土台を固めるJava講座
├─【Javaの基礎知識】Javaとは? Javaの基本概要をわかりやすく解説!
├─【Javaの基礎知識】JDKとEclipseの導入方法を徹底解説!
├─【Javaの基礎知識】変数の使い方・エラー回避・実践例で完全理解!
├─【Javaの基礎知識】演算子の基礎と使い方|条件演算子・new演算子・2乗計算まで解説
├─【Javaの基礎知識】条件分岐を徹底解説:if文とswitch文の使い分けと実践例
├─【Javaの基礎知識】for, while, do-while の違いと使い分け|無限ループを防ぐ実践例
├─【Javaの基礎知識】クラス・オブジェクト・メソッドの基本をわかりやすく解説!
├─【Javaの基礎知識】例外処理とエラー対策を徹底解説!
├─【Javaの基礎知識】配列・コレクション(List・Set・Map)の基本と活用法を解説!
├─【Javaの基礎知識】Javaスレッドの使い方と並行処理の仕組みをわかりやすく解説
└─【Javaの基礎知識】ファイル読み込み・書き込み・削除・出力を完全理解
📌 現場で使える力を。アプリ制作で学ぶ実践型トレーニング
スレッドと並行処理の基礎知識
マルチスレッドを活用することで、アプリケーションのパフォーマンスを向上させることができます。本セクションでは、スレッドの基本概念と並行処理の必要性について解説します。
スレッドとは?
スレッドとは、プログラム内で並行して実行できる最小単位の処理です。一般的なプログラムは1つのスレッド(シングルスレッド)で動作しますが、複数のスレッド(マルチスレッド)を利用することで並行処理が可能になります。
タイプ | 特徴 | 例 |
---|---|---|
シングルスレッド | 1つの処理を順番に実行する | ボタンを押すとアプリが固まる、画像を読み込むまで操作できない |
マルチスレッド | 複数の処理を同時に実行する | ゲームでBGMを流しながらキャラを動かせる、画像を読み込みながらスクロール可能 |
プロセスとスレッドの違い

プロセスとスレッドは似ていますが、明確な違いがあります。
項目 | プロセス | スレッド |
---|---|---|
定義 | OS上で実行される独立したプログラムの単位 | プロセス内で動作する処理の単位 |
メモリ領域 | プロセスごとに独立したメモリを持つ | スレッドはプロセスのメモリを共有する |
処理速度 | プロセス間のデータ共有は遅い | スレッド間のデータ共有は高速 |
通信のしやすさ | プロセス間通信(IPC)が必要 | スレッド間はメモリ共有で簡単に通信可能 |
例えば、Webブラウザはタブごとにプロセスを分けることで、1つのタブがクラッシュしても他のタブに影響を与えないように設計されています。一方、動画再生ソフトは、動画のデコードとオーディオの処理を別々のスレッドで並行処理することでスムーズな再生を実現します。
シングルスレッドとマルチスレッドの比較

スレッドの使用方法には、シングルスレッドとマルチスレッドの2種類があります。
項目 | シングルスレッド | マルチスレッド |
---|---|---|
処理の流れ | 1つのスレッドが順番に処理 | 複数のスレッドが並行して処理 |
メリット | プログラムがシンプルでデバッグしやすい | 高速な処理が可能、待ち時間を削減できる |
デメリット | 処理が遅く、長時間かかる可能性がある | スレッド間の同期が必要で、バグの発生率が高まる |
適用例 | 簡単なスクリプト、シンプルなデスクトップアプリ | ゲーム、Webサーバー、並列計算を行うアプリ |
例えば、画像のダウンロードと表示を同時に行う場合、シングルスレッドだと「ダウンロードが終わるまで画面が固まる」現象が発生しますが、マルチスレッドを利用するとダウンロード中でも画面の操作が可能になります。
シングルスレッドの例


シングルスレッドでは、1つの処理が終わるまで次の処理が実行されません。
public class SingleThreadExample {
public static void main(String[] args) {
System.out.println("画像を読み込みます...");
loadImage(); // 画像を読み込む(ここで処理が止まる)
System.out.println("他の処理をしたいけど、止まってしまう...");
}
static void loadImage() {
try { Thread.sleep(3000); } catch (InterruptedException e) {}
System.out.println("画像の読み込み完了!");
}
}
この場合、画像の読み込みが終わるまでプログラム全体が止まってしまい、他の処理ができません。
マルチスレッドの例


マルチスレッドを使うと、画像の読み込み中でも他の処理を並行して実行できます。
public class MultiThreadExample {
public static void main(String[] args) {
System.out.println("画像を読み込みます...");
new Thread(() -> loadImage()).start(); // 画像読み込みを別のスレッドで実行
System.out.println("画像を読み込みながら他の処理もできる!");
}
static void loadImage() {
try { Thread.sleep(3000); } catch (InterruptedException e) {}
System.out.println("画像の読み込み完了!");
}
}
この場合、画像の読み込みと他の処理を同時に進めることができるため、スムーズな動作を実現できます。
並行処理とは?
並行処理(Concurreny)は、プログラム内で複数の処理を同時に実行することを指します。例えば、Webサーバーが複数のユーザーからのリクエストを処理する際、それぞれのリクエストを独立したスレッドで処理することで、スムーズな応答が可能になります。
並行処理と並列処理の違い

並行処理と並列処理は混同されがちですが、異なる概念です。
項目 | 並行処理(Concurrency) | 並列処理(Parallelism) |
---|---|---|
定義 | 複数の処理を切り替えながら実行する | 複数の処理を同時に実行する |
実行環境 | シングルコアCPUでも実現可能 | マルチコアCPUが必要 |
例 | マルチタスクOS、非同期I/O | 科学計算、機械学習の並列処理 |
例えば、スマホで音楽を聴きながらWebページを閲覧するのは並行処理、画像を高速に処理するために複数のコアを活用するのは並列処理の例です。
並行処理が必要とされるシナリオ
並行処理は以下のような場面で特に有効です。
- Webサーバー: 複数のクライアントからのリクエストを同時に処理
- ゲーム開発: ゲームの描画とユーザー入力を並行して処理
- データ処理: ビッグデータの分析やAI処理の高速化
- 動画再生: 映像と音声の同期処理
例えば、オンラインショッピングサイトでは、ユーザーが商品を検索している間に、裏側で在庫情報を取得するなどの並行処理が行われています。
スレッドが必要な場面(スレッドを使うメリット)
スレッドが必要とされる最大の理由は、ユーザーインターフェースの応答を維持することにあります。
スレッドが登場する以前、シングルコアCPUが一般的だった時代には、一つの処理が完了するまで他の処理が行えず、重い処理を実行するとユーザーインターフェースがフリーズしてしまう問題がありました。
この問題を解決するために、同じCPUコア上でUI処理とバックグラウンド処理を切り分けて実行できる仕組みとしてスレッドが導入されました。
現在はマルチコアCPUが普及したため、スレッドを使うことで複数の処理を物理的に同時実行できるようになりましたが、スレッドを使う本質的な理由は変わらず、UI応答性を維持しつつ効率的な処理を行うことにあります。
アプリの動作をスムーズにする(UIフリーズを防ぐ)


シングルスレッドのプログラムでは、1つの処理が終わるまで他の処理が止まるため、アプリの動作が固まることがあります。スレッドを使うことで、時間のかかる処理をバックグラウンドで実行し、UIの応答性を向上させることができます。
シングルスレッド | マルチスレッド |
---|---|
画像を読み込んでいる間、アプリがフリーズする | 画像を読み込みながらボタンの操作が可能 |
動画を再生すると、他の処理が遅くなる | 動画を再生しながらコメントを入力できる |
import javax.swing.*;
class UIExample {
public static void main(String[] args) {
JFrame frame = new JFrame("スレッド例");
JButton button = new JButton("処理開始");
button.addActionListener(e -> new Thread(() -> {
try { Thread.sleep(5000); } catch (InterruptedException ex) {}
System.out.println("処理完了!");
}).start());
frame.add(button);
frame.setSize(300, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
このように、ボタンを押した後もUIがフリーズせず、スムーズな動作が可能になります。
Webサーバーの同時リクエスト処理


Webサーバーは複数のクライアントからのリクエストを同時に処理する必要があります。シングルスレッドだと、1つのリクエストを処理し終わるまで次のリクエストを待たなければならず、応答速度が低下します。マルチスレッドを活用することで、複数のリクエストを同時に処理でき、スムーズな通信が可能になります。
シングルスレッド | マルチスレッド |
---|---|
1つのリクエストを処理し終わるまで、次のリクエストが待たされる | 複数のリクエストを同時に処理できる |
アクセスが集中するとサーバーが遅くなる | スレッドプールを使って負荷分散が可能 |
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class WebServerExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 1; i <= 10; i++) {
final int requestId = i;
executor.submit(() -> {
System.out.println("リクエスト " + requestId + " を処理中...");
});
}
executor.shutdown();
}
}
スレッドプールを活用することで、同時に複数のリクエストを処理し、サーバーの応答速度を向上させることができます。
データ処理の高速化
大規模なデータ処理を行う場合、1つのスレッドで処理すると時間がかかります。スレッドを分割して処理することで、計算時間を大幅に短縮できます。
シングルスレッド | マルチスレッド |
---|---|
データを順番に処理するため、時間がかかる | 複数のスレッドで同時に処理し、高速化できる |
ビッグデータ解析に時間がかかる | 分散処理で計算時間を短縮できる |
[ コードA ]


Callableインターフェースの実装をラムダ式を使ってmainメソッド内で直接定義。
import java.util.concurrent.*;
class DataProcessingExample {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(4);
Callable
return 100 * 2;
};
Future
System.out.println("計算結果: " + result.get());
executor.shutdown();
}
}
[ コードB ]


Callableインターフェースを別クラス(MyCallable)として実装し、call()メソッド内で戻り値を返す
import java.util.concurrent.*;
class MyCallable implements Callable<Integer> {
public Integer call() throws Exception {
return 42;
}
}
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(new MyCallable());
System.out.println("結果: " + future.get());
executor.shutdown();
このように、複数のスレッドでデータを並列処理することで、処理時間を短縮できます。
コードAとコードBの違い
コードAとコードBはどちらもJavaでCallableとFutureを使った非同期処理を行っていますが、実装方法と設計思想に違いがあります。
コードAでは、Callableインターフェースの実装をラムダ式を使ってmainメソッド内で直接定義しています。これにより短くシンプルに記述でき、処理内容が簡単な場合に適しています。また、Callableの戻り値はFutureを通じて取得しており、その結果をSystem.out.println内でfuture.get()を呼び出すことで出力しています。この形式は、処理内容が局所的で再利用する必要がない場合や、学習時にCallableとFutureの仕組みを理解する際に適しています。
コードBでは、Callableインターフェースを別クラス(MyCallable)として実装し、call()メソッド内で戻り値を返す形を取っています。その上でExecutorServiceにMyCallableのインスタンスをsubmitし、Futureオブジェクトを受け取って結果をfuture.get()で取得しています。この方法は、処理内容が複雑で複数箇所で再利用する場合や、クラス設計の練習をする際に適しています。
[ コードAとコードBの違いまとめ ]
比較項目 | コードA | コードB |
---|---|---|
Callableの実装方法 | ラムダ式でmainメソッド内に局所的に記述 | 別クラスとしてCallableを実装 |
再利用性 | 再利用を前提としない単発処理向け | 複数箇所で再利用可能 |
コード量 | 短く簡潔で理解しやすい | やや長くなるが構造的で保守性が高い |
学習用途 | CallableとFutureの仕組みの基本理解に最適 | クラス設計と再利用を意識した学習向け |
用途 | 簡易な処理・学習用 | 応用・設計練習・実践向け |
スレッドの使い所は?
スレッドは「UIを固めない」「複数処理を並行して時間を短縮する」ために使います。
以下のような場面で実際に使います。
スレッドの使い所
- UIフリーズ回避(非同期処理)
データ取得・重い処理は別スレッドで実行し、結果だけUIスレッドで反映します。 - 大量処理の時間短縮
計算処理やファイルI/Oを複数スレッドで分担し、処理時間を減らします。 - サーバー・通信の同時処理
複数リクエストの同時処理、ダウンロードの並列実行に使います。
Javaにおけるスレッドの実装
Javaでは、スレッドを作成する方法が複数存在します。ここでは、基本的なスレッドの実装方法について詳しく解説します。
Thread クラスの利用
Thread クラスを使ったスレッドの作成方法を紹介します。
Thread クラスを継承する方法
Javaでは、 Thread クラスを継承することでスレッドを作成できます。以下のように、 run() メソッドをオーバーライドすることで、スレッドに実行したい処理を記述できます。
class MyThread extends Thread {
public void run() {
System.out.println("スレッドが実行されています");
}
}
MyThread thread = new MyThread();
thread.start();
Thread クラスを利用した基本的なサンプルコード


以下のコードでは、複数のスレッドを作成し、それぞれ独立した処理を実行させる例を示します。
class MyThread extends Thread {
private String name;
public MyThread(String name) {
this.name = name;
}
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + " の実行: " + i);
}
}
}
MyThread t1 = new MyThread("スレッド1");
MyThread t2 = new MyThread("スレッド2");
t1.start();
t2.start();
Runnable インターフェースの利用
Runnable インターフェースを使ってスレッドを作成する方法を紹介します。
Runnableインターフェースは、Javaでスレッド処理を実装するための仕組みで、runメソッドを実装することでスレッド内で実行する処理を記述できます。
Javaは単一継承であるため、Threadクラスを継承してしまうと他のクラスを継承できなくなりますが、Runnableはインターフェースのため柔軟に実装できるという特徴があります。
このため、現場でスレッド処理を実装する際にはThreadクラスよりもRunnableインターフェースを使うことが一般的です。
Runnable 実装のメリット
Javaでは、 Thread クラスを継承する代わりに Runnable インターフェースを実装することで、スレッドを作成できます。この方法のメリットは、Javaの「単一継承の制限」に引っかからないことです。
Runnable を活用した実装例


以下のコードでは、 Runnable を実装したクラスを使い、スレッドを作成する例を示します。
class MyRunnable implements Runnable {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("スレッドの実行: " + i);
}
}
}
Thread thread = new Thread(new MyRunnable());
thread.start();
Callable と Future の活用
Callable インターフェースと Future を使った非同期処理の実装方法を解説します。
CallableとFutureは、Javaで非同期処理を行う際に使う仕組みです。CallableはRunnableと同じくスレッドで処理を実行するための仕組みですが、Runnableと異なり戻り値を返すことができる特徴があります。
また、Callableは例外をスローすることも可能で、処理中に発生したエラーを適切に扱うことができます。
FutureはCallableの処理結果を後から受け取るための仕組みであり、時間のかかる処理を非同期で実行しつつ、必要なタイミングでその結果を取得することができます。
これにより、プログラム全体の処理を止めることなく、効率的に結果を取得できるようになります。
非同期で値を返せる仕組み
Javaで非同期処理を行う際に使われるのがCallableとFutureです。Runnableはスレッドで処理を並行実行できますが、実行後の戻り値を呼び出し元へ返すことはできません。
これに対し、Callableはcall()メソッドを持ち、処理結果として戻り値を返せるようになっています。CallableをExecutorServiceに渡して処理を実行すると、戻り値の受け取り口としてFutureオブジェクトが返されます。
ExecutorServiceは処理を非同期で実行するため、呼び出し元のプログラムはその間他の処理を進めることが可能です。必要なタイミングでFuture.get()を呼び出すと、Callableで処理した結果を取得できる仕組みになっています。
この流れによって「処理自体は非同期で実行されつつ、処理が終わったタイミングで結果を受け取る」という動作が可能になり、非同期処理を効率的に行いながら結果を活用できるようになります。
Callable と Runnable の違い
Runnable は戻り値を返せませんが、 Callable は戻り値を返すことができます。この違いを活かし、結果を取得できるスレッドを作成できます。
Future を用いた並行処理の実装例


CallableとFutureの仕組みを理解したところで、実際にJavaで非同期処理を行い、処理結果を後で受け取る実装例を紹介します。
以下のサンプルでは、Callableで整数の合計値を計算し、その結果をFutureで受け取る流れを示しています。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class FutureExample {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable task = () -> {
int sum = 0;
for (int i = 1; i <= 10; i++) {
sum += i;
}
return sum;
};
Future future = executor.submit(task);
System.out.println("計算中...他の処理を実行可能");
Integer result = future.get();
System.out.println("計算結果: " + result);
executor.shutdown();
}
}
この例ではExecutorServiceを使ってCallableタスクを非同期で実行し、その処理結果をFuture.get()で取得しています。
Future.get()を呼び出したタイミングで処理が完了していない場合は完了するまで待機し、完了していればすぐに結果が返されます。
これにより、時間のかかる計算処理をバックグラウンドで実行しながら、他の処理を同時に行うことが可能となり、効率的な並行処理を実現できます。
以下のコードでは、 Callable を使って計算結果を取得するスレッドを作成する例を示します。
まとめ
本記事ではJavaのスレッドの基礎として、スレッドが必要な理由、使う場面、そして基本的な実装方法を解説しました。
スレッドを活用することで、時間がかかる処理を別スレッドで実行し、アプリの応答性を保ちながら効率的な処理が可能になります。
まずはThread、Runnable、CallableとFutureの使い方を理解し、スレッドを使った並行処理を自分のプログラムで試してみましょう。
スレッドは「プログラムを速くする魔法」ではなく、「適切に使わないと逆に遅くなる」こともあります。まずは **「スレッドが必要な場面」と「注意点」** を押さえておきましょう!
この記事を読んだら、次は 「【Javaの基礎知識】ファイル読み込み・書き込み・削除・出力を完全理解」 を読むのがおすすめです!