Java 自學筆記 06 - Multi Thread
Java Thread
There are two ways to new a thread in Java:
Implemented from Runnable Interface
Runnable 是一個 interface,run()
裡面放的就是 thread 要執行的內容,一定要 override 且一定要設為 public。1
2
3
4
5
6
7
8
9class TestRunnable implements Runnable{
public void run(){
// ...
}
}
// to new
Thread thread = new Thread(new TestRunnable());
Extended from Thread Class
Thread 是 Runnable 的實現,用法一樣是把要執行的程式放進 run()
裡面,概念一樣是 override + public。1
2
3
4
5
6
7
8
9class TestThread extends Thread{
public void run(){
// ...
}
}
// to new
Thread thread = new TestThread()
我們也可以直接用 lambda 來 new thread。1
2
3Thread thread = new Thread(()->{
// ...
});
Start and Join
我們可以用 start()
來開始執行 thread,並用 join()
來等待 thread 完成並回歸 main thread。1
2
3
4
5
6
7
8
9
10
11
12Thread thread = new Thread(()->{
System.out.println("Run in thread");
});
System.out.println("Run in main");
thread.start();
try{
thread.join();
}catch(InterruptedException e){ // 一定要處理 Exception
// ...
}
System.out.println("Back to main");
執行結果如下:1
2
3Run in main
Run in thread
Back to main
Shared Resources
同時有多個 thread 對同一個資源進行操作時,可能會出現 race condition,導致結果不如預期,舉例來說:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29class SyncThread extends Thread{
static int sum = 0;
public void run(){
for (int i=0; i<10000; i++){
addOne();
}
}
void addOne(){
sum += 1;
}
}
public class SharedResources {
public static void main(String[] args){
Thread thread1 = new SyncThread();
Thread thread2 = new SyncThread();
thread1.start();
thread2.start();
try{
thread1.join();
thread2.join();
}catch(InterruptedException e){
// ...
}
System.out.println(SyncThread.sum);
}
}
每一個 thread 各別對 sum
進行 addOne()
10000 次,理論上最後的結果應該是sum = 20000
,但最後結果卻小於 20000 (每次跑會不太一樣),原因大致上是這樣:
Synchronized
我們可以使用 synchronized
關鍵字來實現同步,確保在同一時間只有一個 thread 在執行指定的代碼,使用方法有:1
2
3
4
5
6
7
8
9
10
11// 1. 修飾方法
synchronized void addOne(){
sum += 1;
}
// 2. 修飾代碼塊
void addOne(){
synchronized (this){
sum += 1;
}
}
不過 synchronized
是比較古老的用法了,在高併發的情況下性能可能會不太好,所以可以考慮用後面的方法。
Atomic
Atomic 表示不可被切分的動作,以我們的例子來說就是,我們可以將 sum
指定為 AtomicInteger,確保對 sum
的操作不會被中斷。1
2
3
4
5
6
7
8
9
10
11
12import java.util.concurrent.atomic.AtomicInteger;
class SyncThread extends Thread {
static AtomicInteger sum = new AtomicInteger(0);
public void run() {
for (int i = 0; i < 10000; i++) {
sum.addAndGet(1);
}
}
}
Concurrent Lock
java.util.concurrent
是 Java 中用來取代 synchronized
的一個 package,我們可以使用裡面的 Lock 機制,透過 Lock 的取得以及釋放來控制 thread 的動作。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class SyncThread extends Thread {
static int sum = 0;
private static final Lock lock = new ReentrantLock();
public void run() {
for (int i = 0; i < 10000; i++) {
addOne();
}
}
void addOne() {
lock.lock();
try {
sum += 1;
} finally {
lock.unlock();
}
}
}
Concurrent Collection
前兩篇在介紹 Collections 的時候有稍微提到,Vector、Hashtable 等容器透過 synchronized
來實現同步,在高併發時效率不太好,因此 Java 後來又提供了 ConcurrentHashMap、ConcurrentLinkedQueue 等基於 concurrent 實現的容器。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import java.util.concurrent.ConcurrentHashMap;
class SyncThread extends Thread {
static ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(){
{
put("sum", 0);
}
};
public void run() {
for (int i = 0; i < 10000; i++) {
map.compute("sum", (key, value) -> value + 1);
}
}
}