こんにちは、コイキングです。
本記事では'スレッド'について、まとめてみます。
1. プロセスとスレッド
プロセス : 実行中のプログラム。
スレッド : プロセス内で実行されるフローの単位。
マルチスレッド : プロセス内に複数のスレッドが存在すること。
2. Javaでスレッドを使う方法
1) マルチスレッドを使わない。
package thread.ex01;
public class ThreadMain01 {
public static void main(String[] args) {
Calculator calculator = new Calculator(0L, 1000000000L);
execute(calculator);
}
public static void execute(Calculator calculator) {
long startTime = System.currentTimeMillis();
System.out.println("StartTime : "+ startTime);
calculator.calc();
long result = (long) calculator.getSum();
long endTime = System.currentTimeMillis();
System.out.println("EndTime : "+ endTime);
System.out.printf("Operating Time : [%.3f] Sec, Result : [%d] \n", (endTime - startTime) / 1000.0, result);
}
}
class Calculator {
private long start;
private long end;
private long sum;
public Calculator(long start, long end) {
setInit(start, end);
}
public void calc() {
long sum = 0;
for (long i = start; i <= end; i++) {
sum += i;
}
this.sum = sum;
}
public long getSum() {
return this.sum;
}
public void setInit(long start, long end) {
this.start = start;
this.end = end;
this.sum = 0;
}
}
// 実行結果
//StartTime : 1643158486533
//EndTime : 1643158487070
//Operating Time : [0.537] Sec, Result : [500000000500000000]
- 指定した始まりの数から最後の数まで重ねて合算するコードです。
2) Threadクラスを使う。
package thread.ex01;
import java.util.ArrayList;
public class ThreadMain02 {
public static void main(String[] args) {
// 非同期問題をjoinメソッドを使い飛ばす
System.out.println("########## Solved concurrency issue. ##########");
execute(5, 1000000000L, true);
System.out.printf("\n\n");
// 非同期問題のため、合算値が違う
System.out.println("########## Not Solved concurrency issue. ##########");
execute(5, 1000000000L);
}
public static void execute(int worker, long input) {
execute(worker, input, false);
}
public static void execute(int worker, long input, boolean isJoin) {
ArrayList<Calculator2> list = new ArrayList<Calculator2>();
long startTime = System.currentTimeMillis();
System.out.println("StartTime : "+ startTime);
long start = 0;
long end = input / worker;
for (int i=1; i <= worker; i++) {
if (input < end) end = input;
Calculator2 calc = new Calculator2(start , end, i);
calc.start();
list.add(calc);
if (end == input) break;
start = end + 1;
end = end * 2;
}
long result = 0;
for (int i=0; i < list.size(); i++) {
Calculator2 calc = list.get(i);
try {
if (isJoin) {
calc.join();
}
result += calc.getSum();
} catch (Exception e) {}
}
long endTime = System.currentTimeMillis();
System.out.println("EndTime : "+ endTime);
System.out.printf("Operating Time : [%.3f] Sec, Result : [%d] \n", (endTime - startTime) / 1000.0, result);
list.clear();
list = null;
}
}
class Calculator2 extends Thread {
private long start;
private long end;
private long sum;
private int num;
public Calculator2(long start, long end, int num) {
setInit(start, end, num);
}
public void calc() {
System.out.printf("Thread [%d] Before, Num [%d], End Num [%d], sum() : [%d] \n", num, start, end, start);
long sum = 0;
for (long i = start; i <= end; i++) {
sum += i;
}
System.out.printf("Thread [%d] After, Num [%d], End Num [%d], sum() : [%d] \n", num, start, end, sum);
this.sum = sum;
}
public long getSum() {
return this.sum;
}
public void setInit(long start, long end, int num) {
this.start = start;
this.end = end;
this.sum = 0;
this.num = num;
}
@Override
public void run() {
calc();
}
}
// 実行結果
//########## Solved concurrency issue. ##########
//StartTime : 1643159110620
//Thread [4] Before, Num [800000001], End Num [1000000000], sum() : [800000001]
//Thread [1] Before, Num [0], End Num [200000000], sum() : [0]
//Thread [3] Before, Num [400000001], End Num [800000000], sum() : [400000001]
//Thread [2] Before, Num [200000001], End Num [400000000], sum() : [200000001]
//Thread [1] After, Num [0], End Num [200000000], sum() : [20000000100000000]
//Thread [4] After, Num [800000001], End Num [1000000000], sum() : [180000000100000000]
//Thread [2] After, Num [200000001], End Num [400000000], sum() : [60000000100000000]
//Thread [3] After, Num [400000001], End Num [800000000], sum() : [240000000200000000]
//EndTime : 1643159110874
//Operating Time : [0.254] Sec, Result : [500000000500000000]
//
//
//########## Not Solved concurrency issue. ##########
//StartTime : 1643159110876
//EndTime : 1643159110876
//Operating Time : [0.000] Sec, Result : [0]
//Thread [4] Before, Num [800000001], End Num [1000000000], sum() : [800000001]
//Thread [3] Before, Num [400000001], End Num [800000000], sum() : [400000001]
//Thread [2] Before, Num [200000001], End Num [400000000], sum() : [200000001]
//Thread [1] Before, Num [0], End Num [200000000], sum() : [0]
//Thread [1] After, Num [0], End Num [200000000], sum() : [20000000100000000]
//Thread [4] After, Num [800000001], End Num [1000000000], sum() : [180000000100000000]
//Thread [2] After, Num [200000001], End Num [400000000], sum() : [60000000100000000]
//Thread [3] After, Num [400000001], End Num [800000000], sum() : [240000000200000000]
- 単一スレッドを使ったコードをマルチスレッドを使い、実装したものです。
実行結果をみると単一スレッドより、処理速度が速いのが分かります。
(1) マルチスレッドの長所
長所 :
- 単一スレッドで処理することより、マルチスレッドを使い並列で処理すればより速く処理できるかもしれない。(状況によって違う。。)
- どちらかのスレッドが止まってしまっても、他のスレッドが止まらないとプログラムとして機能できるかもしれない。
短所 :
- プログラムの性能が低下する可能性があり。
(例1) 臨界領域の問題を解決するため、不要な所まで同期化をする場合。
(例2) 余りにも多くスレッドを生成する場合、コンテキストスイッチやスレッドのインスタンスを生成することでリソースを消耗しすぎってしまう。 - 非同期問題が発生する可能性がある。(例) 複数のスレッドで作業した結果を一つに合わせる場合。
-> Threadクラスのjoinメソッドを使って、解決する。 - 臨海領域問題が発生する可能性がある。(例) 複数のスレッドから一つの変数に同時にアクセスする場合。
-> synchronizedキーワードを使って同期化して解決する。
※ 非同期問題の臨海領域問題解決例示コード
package thread.ex01;
import java.util.ArrayList;
public class ThreadMain04 {
public static int sharedNum = 0;
public static void main(String[] args) {
SyncData sd = new SyncData(0);
ArrayList<SyncTest> list = new ArrayList<SyncTest>();
for (int i=1; i<=3; i++) {
SyncTest sync = new SyncTest(i, sd);
sync.start();
list.add(sync);
}
for (SyncTest sync : list) {
try {
sync.join();
} catch (InterruptedException e) { e.printStackTrace(); }
}
System.out.println("Test1-Result : "+sd.balance);
System.out.printf("\n\n");
ArrayList<SyncTest2> list2 = new ArrayList<SyncTest2>();
for (int i=1; i<=3; i++) {
SyncTest2 sync2 = new SyncTest2(i);
sync2.start();
list2.add(sync2);
}
for (SyncTest2 sync2 : list2) {
try {
sync2.join();
} catch (InterruptedException e) { e.printStackTrace(); }
}
System.out.println("Test2-Result : "+sharedNum);
}
}
class SyncData {
Integer balance;
public SyncData(int balance) {
this.balance = balance;
}
}
class SyncTest extends Thread{
SyncData syncData;
int number;
public SyncTest(int number, SyncData syncData) {
this.number = number;
this.syncData = syncData;
}
@Override
public void run() {
sum();
}
public void sum() {
System.out.println("sum() Thread ["+number+"] Before : "+syncData.balance);
for (int i=1; i<=3; i++) {
synchronized (this) {
syncData.balance += i;
System.out.println("sum() Thread ["+number+"] After : "+syncData.balance);
}
}
}
}
class SyncTest2 extends Thread {
int number;
public SyncTest2(int number) {
this.number = number;
}
@Override
public void run() {
sum();
}
public synchronized void sum() {
System.out.println("sum() Thread ["+number+"] Before : "+ThreadMain04.sharedNum);
for (int i=1; i<=3; i++) {
ThreadMain04.sharedNum += i;
System.out.println("sum() Thread ["+number+"] After : "+ThreadMain04.sharedNum);
}
}
}
3) Runnableインターフェースを使う
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
public class ThreadMain03 {
public static void main(String[] args) throws InterruptedException {
PrintTime pt = new PrintTime();
Thread t = new Thread(pt);
t.start();
for (int i=0; i<20; i++) {
System.out.println("test"+i);
Thread.sleep(700);
}
}
}
class PrintTime implements Runnable {
static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS");
@Override
public void run() {
for (int i=0; i<10; i++) {
System.out.println(sdf.format(new Date()));
try {
Thread.sleep(300);
} catch (InterruptedException e) { e.printStackTrace(); }
}
}
}
Threadクラスでスレッドを使う場合、継承が使えない短所があります。
Threadクラスではなく、他のクラスを継承したい場合はRunnableインターフェースを使えばいいです。
※ ThreadクラスはRunnableインターフェースを継承したクラスだった。。
4) Callableインターフェースを使う
package thread.ex01;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
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;
import java.util.concurrent.FutureTask;
public class ThreadMain05 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
GetTime gt = new GetTime();
System.out.println("########## Callable Test (1) ##########");
FutureTask<ArrayList<String>> futureTask = new FutureTask<ArrayList<String>>(gt);
new Thread(futureTask).start();
for (int i=0; i<5; i++) {
System.out.println("test1 wating Count : "+i);
Thread.sleep(500);
}
System.out.println(futureTask.get());
System.out.println("########## Callable Test (2) ##########");
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<ArrayList<String>> future = executor.submit(gt);
for (int i=0; i<5; i++) {
System.out.println("test2 wating Count : "+i);
Thread.sleep(300);
}
System.out.println(future.get());
}
}
class GetTime implements Callable<ArrayList<String>> {
static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS");
@Override
public ArrayList<String> call() throws Exception {
ArrayList<String> list = new ArrayList<String>();
for (int i=0; i<5; i++) {
String time = sdf.format(new Date());
System.out.println(time);
list.add(time);
try {
Thread.sleep(300);
} catch (InterruptedException e) { e.printStackTrace(); }
}
return list;
}
}
Runnable インターフェースのrun()メソッドは戻り値を持つ事ができないし、例外処理を付けられません。
スレッドの各作業の戻り値と例外処理をする必要があるプログラムを実装する時はCallableインターフェースを使えばよいです。
5) Thread Poolを使う
package thread.ex01;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ThreadMain06 {
public static void main(String[] args) throws InterruptedException{
// 決めた数分スレッドを生成
int workers = 4;
ExecutorService executorService = Executors.newFixedThreadPool(workers);
for (int i=1; i<=10; i++) {
ThreadPoolTest threadPoolTest = new ThreadPoolTest(i);
executorService.submit(threadPoolTest);
}
if (executorService.awaitTermination(5, TimeUnit.SECONDS)) {
System.out.println("All Tasks over");
}
executorService.shutdown();
}
}
class ThreadPoolTest implements Runnable {
int number;
@Override
public void run() {
counting();
}
public ThreadPoolTest(int number) {
this.number = number;
}
public void counting() {
for (int i=0; i<5; i++) {
System.out.printf("Thread [%d], Count [%d] \n", number, i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) { e.printStackTrace(); }
}
}
}
スレッドフルは予め決めた数のスレッドを生成して置いて、使えることにするものです。
マルチスレッドの短所のうち、余りにも多くスレッドを生成する場合にプログラムの性能が低下する短所を解決するために使えばよいです。
※ 例示コード
https://github.com/leeyoungseung/algorithmBasic/tree/master/algorithm/src/thread/ex01
※ 韓国語のポストは以下のURLで確認できます。
https://koiking.tistory.com/93