13-4. シングルスレッドとマルチスレッド


シングルスレッドとマルチスレッド
2つのタスクは、2つのスレッドではなく1つのスレッドとして処理されます.
単一スレッドを使用すると、1つのタスクが完了した後に次のタスクを開始できますが、2つのスレッドを使用すると、タスクを同時に処理するように2つのタスクが交互に実行されます.
しかし、2つのスレッドがある場合、逆に、スレッド間の変換(Context Switching)が単一スレッドよりも長い時間を要することになる.
Context Switching
進行中のタスク(プロセスまたはスレッド)のステータスを保存し、次のタスクのステータスを取得します.
したがって、マルチスレッドは必ずしも良いわけではありません.場合によっては、単一スレッドでプログラミングしたほうがいいかもしれません.
 
単一スレッドを使用した2つのタスク
class SingleThreadExample {
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();

        for (int i = 0; i < 2000; i++) {
            System.out.printf("%s", new String("-"));
            // printf()와 new String()을 사용해서 고의로 속도를 늦췄음.
        }
        System.out.println("소요 시간1: " + (System.currentTimeMillis() - startTime));

        for (int i = 0; i < 2000; i++) {
            System.out.printf("%s", new String("|"));
        }
        System.out.println("소요 시간2: " + (System.currentTimeMillis() - startTime));
    }
}
実行結果)
(너무 길어서 앞부분 생략)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------소요 시간1: 21
(여기도 앞부분 생략)
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||소요 시간2: 32
Process finished with exit code 0
2つのタスクを完了するには32ミリ秒かかります
 
2つのマルチスレッド(2つ)を使用して2つのタスクを実行
class MultiThreadExample {
    static long startTime = 0;

    public static void main(String[] args) {
        Thread1 t1 = new Thread1();
        t1.start();
        startTime = System.currentTimeMillis();
        for (int i = 0; i < 2000; i++) {
            System.out.printf("%s", new String("-"));
        }
        System.out.println("소요 시간1: " + (System.currentTimeMillis() - startTime));
    }
}

class Thread1 extends Thread {
    public void run() {
        for (int i = 0; i < 2000; i++) {
            System.out.printf("%s", new String("|"));
        }
        System.out.println("소요 시간2: " + (System.currentTimeMillis() - MultiThreadExample.startTime));
    }
}
実行結果)
(너무 길어서 앞부분 생략)----||||-----|||-------|||||----------------------||---------------|||--|||||--|||---------------------||||---------||---||----|||||---||||||||--||||------||--|||||||||||||-----||||||||||---------------------------||||||||||||||||---------|||||||||||||||||--------------||---|||-----||---||--|||||||||||||||||||||||||||----|||||||||||||||||||||------||||||||||---------------------------------------------------------------------------------------------------------------------------------------------------------------------------소요 시간2: 40
소요 시간1: 41
2つのタスクを完了するには41ミリ秒かかります
8コアCPU環境でテストが行われているため、各コアはスレッドを1つ実行し、同時に2つのタスクを実行します.
「出力スクリーン」(console)という名前のリソースを共有するため、各スレッドがリソースを争っており、-|の出力が雑然と交互に現れる.
 
結論:同じリソースを使用する2つのタスクを処理する場合、単一スレッドの速度はより速くなります.
 
📌 [注意]
パラレル(concurrent):単一カーネルで、タスクを複数のスレッドに分割し、順番に実行します.(物理的に同時に行われる操作ではありませんが、交代速度が速いので同時に行われているように見えます)
パラレル(Parallel):マルチコア環境では、タスクを複数のカーネルに分散し、各カーネルのスレッドを同時に処理します(実際には複数のタスクを同時に実行します).
 
📌 [注意]
実行中のサンプル・プロセスは、オペレーティング・システムのプロセス・スケジューラの影響を受けるため、サンプルの実行結果で測定される時間は毎回異なります.
プロセススケジューラは、プロセスの実行順序と実行時間を決定します.これは、毎回の状況に応じて異なり、スレッドに割り当てられる時間も固定されません.
 
異なるリソースを使用する2つのタスクを処理すると、マルチスレッドの速度が速くなります.
ex)ユーザからのデータ入力の受信、ファイルのネットワークへの転送、プリンタなどの外部機器とのI/O…
 
ユーザから入力値を受信して出力し、10から1秒ごとに数値を数えます.
class ThreadExample {
    public static void main(String[] args) throws Exception{
        String input = JOptionPane.showInputDialog("Enter any value");
        System.out.println("Your input is: " + input);

        for (int i = 10; i > 0; i--) {
            System.out.println(i);
            try {
                Thread.sleep(1000); // 1초 동안 시간 지연
            } catch (Exception e) {}
        }
    }
}
実行結果)
Your input is: hello
10
9
8
7
6
5
4
3
2
1
入力と出力はまったく異なる操作であり、共有リソースはありません.
ただし、単一スレッドであるため、入力値を受信するまで他の操作を実行できないため、効率は非常に低い.
 
class ThreadExample {
    public static void main(String[] args) throws Exception{
        Thread1 t1 = new Thread1();
        t1.start();

        String input = JOptionPane.showInputDialog("Enter any value");
        System.out.println("Your input is: " + input);
    }
}

class Thread1 extends Thread {
    public void run() {
        for (int i = 10; i > 0; i--) {
            System.out.println(i);
            try {
                Thread.sleep(1000);
            } catch (Exception e) {}
        }
    }
}
実行結果)
10
9
8
7
6
Your input is: hello
5
4
3
2
1
今回は、2つのタスクがそれぞれ異なるスレッドで実行されるため、入力値が入力であれ出力であれ、数値カウントが行われます.
スレッドの空き時間が解消されるため、CPU利用率が向上する.