ConcurrentHashMap
?它與HashMap
的區別是什麼?CopyOnWriteArrayList
?它適用於什麼樣的場景?BlockingQueue
?它的作用是什麼?舉例說明一個使用場景。Semaphore
?它如何控制並行存取?CountDownLatch
?它適用於什麼場景?CyclicBarrier
?它適用於什麼場景?Semaphore
?它的作用是什麼?Future
和FutureTask
?它們有什麼作用?Executor
框架?如何使用它來管理執行緒池?ScheduledExecutorService
?它用於什麼場景?ThreadLocal
?它的作用是什麼?有何注意事項?Atomic
類提供了哪些原子操作?Lock
介面?它與synchronized
關鍵字的區別是什麼?ReadWriteLock
?它如何在讀寫操作上提供更好的效能?Exchanger
?它的作用是什麼?Semaphore
?它的作用是什麼?BlockingQueue
?它的作用是什麼?舉例說明一個使用場景。CompletableFuture
?它的作用是什麼?舉例說明一個使用場景。StampedLock
?它的作用是什麼?ForkJoinPool
?它適用於什麼場景?CyclicBarrier
?它的作用是什麼?CountDownLatch
?它的作用是什麼?Phaser
?它的作用是什麼?BlockingDeque
?它與BlockingQueue
有何不同?TransferQueue
?它的作用是什麼?ScheduledExecutorService
?它的作用是什麼?ForkJoinTask
?它的作用是什麼?CompletableFuture
的組合操作?ForkJoinTask
的工作竊取機制?ConcurrentHashMap
與HashTable
之間的區別是什麼?Exchanger
?它的作用是什麼?BlockingQueue
與Exchanger
之間的區別是什麼?Semaphore
的公平性?ConcurrentHashMap
如何保證執行緒安全?StampedLock
的樂觀讀?Semaphore
?它的作用是什麼?ThreadLocal
?它的作用是什麼?CompletableFuture
如何處理異常?StampedLock
的樂觀讀和悲觀讀有什麼區別?CountDownLatch
與CyclicBarrier
之間的區別是什麼?Semaphore
和ReentrantLock
之間的區別是什麼?Semaphore
的公平性與非公平性有什麼區別?ReentrantReadWriteLock
是什麼?它的作用是什麼?Phaser
和CyclicBarrier
之間的區別是什麼?Exchanger
和TransferQueue
之間的區別是什麼?BlockingQueue
和TransferQueue
之間的區別是什麼?CompletableFuture
和Future
之間的區別是什麼?Semaphore
和Mutex
之間有什麼區別?ReadWriteLock
和StampedLock
之間的區別是什麼?BlockingQueue
和SynchronousQueue
之間的區別是什麼?CopyOnWriteArrayList
和ArrayList
之間的區別是什麼?ForkJoinPool
是什麼?它的作用是什麼?CompletableFuture
的thenCompose
和thenCombine
有什麼區別?StampedLock
的樂觀讀和悲觀讀有什麼區別?ThreadPoolExecutor
中的核心執行緒數和最大執行緒數的區別是什麼?ThreadPoolExecutor
中的拒絕策略有哪些?如何選擇合適的拒絕策略?ForkJoinTask
的fork()
和join()
方法有什麼作用?ThreadLocal
是什麼?它的作用是什麼?ThreadLocal
的記憶體漏失問題如何避免?Thread.sleep()
和Object.wait()
有什麼區別?volatile
關鍵字的作用是什麼?它解決了什麼問題?synchronized
關鍵字和ReentrantLock
有什麼區別?volatile
關鍵字和synchronized
關鍵字有什麼區別?CountDownLatch
和CyclicBarrier
有什麼區別?ThreadLocal
實現執行緒間的資料隔離?volatile
關鍵字的作用是什麼?它解決了哪些問題?volatile
關鍵字和synchronized
關鍵字有什麼區別?ThreadLocal
實現執行緒間的資料隔離?提到多執行緒,當然要熟悉java提供的各種多執行緒相關的並行包了,而java.util.concurrent就是最最經常會使用到的,那麼關於concurrent的面試題目有哪些呢?一起來看看吧。
ConcurrentHashMap
?它與HashMap
的區別是什麼?回答: ConcurrentHashMap
是java.util.concurrent
包中的一個執行緒安全的雜湊表實現。與普通的HashMap
相比,ConcurrentHashMap
在多執行緒環境下提供更好的效能和執行緒安全保障。
區別:
ConcurrentHashMap
支援並行讀寫操作,而HashMap
在多執行緒環境下需要額外的同步措施。
ConcurrentHashMap
的put
、remove
等操作使用分段鎖,只鎖定部分資料,從而提高並行度。
ConcurrentHashMap
允許多個執行緒同時進行讀操作,而HashMap
在讀寫衝突時需要互斥。
範例:
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
ConcurrentHashMap<Integer, String> concurrentMap = new ConcurrentHashMap<>();
concurrentMap.put(1, "One");
concurrentMap.put(2, "Two");
concurrentMap.put(3, "Three");
String value = concurrentMap.get(2);
System.out.println("Value at key 2: " + value);
}
}
CopyOnWriteArrayList
?它適用於什麼樣的場景?回答: CopyOnWriteArrayList
是java.util.concurrent
包中的一個執行緒安全的動態陣列實現。它適用於讀多寫少的場景,即在讀操作遠遠多於寫操作的情況下,使用CopyOnWriteArrayList
可以避免讀寫衝突。
CopyOnWriteArrayList
在寫操作時會建立一個新的陣列,複製舊陣列中的資料,並新增新的元素,然後將新陣列替換舊陣列。因此,寫操作不會影響讀操作,讀操作也不會影響寫操作。
範例:
import java.util.concurrent.CopyOnWriteArrayList;
public class CopyOnWriteArrayListExample {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("One");
list.add("Two");
list.add("Three");
for (String item : list) {
System.out.println(item);
}
}
}
BlockingQueue
?它的作用是什麼?舉例說明一個使用場景。回答: BlockingQueue
是java.util.concurrent
包中的一個介面,表示一個支援阻塞的佇列。它的主要作用是實現執行緒間的資料傳遞和共同作業。
BlockingQueue
可以用於解耦生產者和消費者,讓生產者和消費者執行緒在不同的速度進行操作。當佇列為空時,消費者執行緒會阻塞等待,直到佇列中有資料;當佇列滿時,生產者執行緒會阻塞等待,直到佇列有空間。
範例:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class BlockingQueueExample {
public static void main(String[] args) {
BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
Thread producer = new Thread(() -> {
try {
queue.put("Item 1");
queue.put("Item 2");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread consumer = new Thread(() -> {
try {
String item1 = queue.take();
String item2 = queue.take();
System.out.println("Consumed: " + item1 + ", " + item2);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
producer.start();
consumer.start();
}
}
Semaphore
?它如何控制並行存取?回答: Semaphore
是java.util.concurrent
包中的一個計數號誌。它可以用來控制同時存取某個資源的執行緒數量,從而實現對並行存取的控制。
Semaphore
通過呼叫acquire()
來獲取一個許可證,表示可以存取資源,通過呼叫release()
來釋放一個許可證,表示釋放資源。Semaphore
的內部計數器可以控制同時獲取許可證的執行緒數量。
範例:
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(2); // 允許兩個執行緒同時存取
Thread thread1 = new Thread(() -> {
try {
semaphore.acquire();
System.out.println("Thread 1 acquired a permit.");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
System.out.println("Thread 1 released a permit.");
}
});
Thread thread2 = new Thread(() -> {
try {
semaphore.acquire();
System.out.println("Thread 2 acquired a permit.");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
System.out.println("Thread 2 released a permit.");
}
});
thread1.start();
thread2.start();
}
}
CountDownLatch
?它適用於什麼場景?回答: CountDownLatch
是java.util.concurrent
包中的一個計數器,用於控制執行緒等待其他執行緒完成一組操作。它適用於一個執行緒需要等待其他多個執行緒完成某個任務後再繼續執行的場景。
CountDownLatch
的內部計數器可以初始化為一個正整數,每個執行緒完成一個操作後,呼叫countDown()
方法來減少計數器的
值。當計數器減為0時,等待的執行緒將被釋放。
範例:
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(3); // 需要等待3個執行緒完成
Thread worker1 = new Thread(() -> {
System.out.println("Worker 1 is working...");
latch.countDown();
});
Thread worker2 = new Thread(() -> {
System.out.println("Worker 2 is working...");
latch.countDown();
});
Thread worker3 = new Thread(() -> {
System.out.println("Worker 3 is working...");
latch.countDown();
});
worker1.start();
worker2.start();
worker3.start();
try {
latch.await(); // 等待所有工作執行緒完成
System.out.println("All workers have completed.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
CyclicBarrier
?它適用於什麼場景?回答: CyclicBarrier
是java.util.concurrent
包中的一個同步工具,用於等待一組執行緒都達到某個狀態後再繼續執行。它適用於需要多個執行緒協同工作的場景,比如將多個子任務的計算結果合併。
CyclicBarrier
的內部計數器初始化為一個正整數,每個執行緒到達屏障時,呼叫await()
方法來等待其他執行緒,當所有執行緒都到達時,屏障開啟,所有執行緒繼續執行。
範例:
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(3); // 需要3個執行緒都到達屏障
Thread thread1 = new Thread(() -> {
System.out.println("Thread 1 is waiting at the barrier.");
try {
barrier.await();
System.out.println("Thread 1 has passed the barrier.");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
Thread thread2 = new Thread(() -> {
System.out.println("Thread 2 is waiting at the barrier.");
try {
barrier.await();
System.out.println("Thread 2 has passed the barrier.");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
Thread thread3 = new Thread(() -> {
System.out.println("Thread 3 is waiting at the barrier.");
try {
barrier.await();
System.out.println("Thread 3 has passed the barrier.");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
thread1.start();
thread2.start();
thread3.start();
}
}
Semaphore
?它的作用是什麼?回答: Semaphore
是java.util.concurrent
包中的一個計數號誌。它可以用來控制同時存取某個資源的執行緒數量,從而實現對並行存取的控制。
Semaphore
通過呼叫acquire()
來獲取一個許可證,表示可以存取資源,通過呼叫release()
來釋放一個許可證,表示釋放資源。Semaphore
的內部計數器可以控制同時獲取許可證的執行緒數量。
範例:
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(2); // 允許兩個執行緒同時存取
Thread thread1 = new Thread(() -> {
try {
semaphore.acquire();
System.out.println("Thread 1 acquired a permit.");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
System.out.println("Thread 1 released a permit.");
}
});
Thread thread2 = new Thread(() -> {
try {
semaphore.acquire();
System.out.println("Thread 2 acquired a permit.");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
System.out.println("Thread 2 released a permit.");
}
});
thread1.start();
thread2.start();
}
}
Future
和FutureTask
?它們有什麼作用?回答: Future
是java.util.concurrent
包中的一個介面,表示一個非同步計算的結果。FutureTask
是Future
的一個實現類,用於將一個Callable
任務包裝為一個非同步計算。
通過Future
,可以提交一個任務給執行緒池或其他並行框架執行,並在未來的某個時刻獲取任務的計算結果。
範例:
import java.util.concurrent.*;
public class FutureExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(() -> {
Thread.sleep(2000);
return 42;
});
System.out.println("Waiting for the result...");
try {
Integer result = future.get();
System.out.println("Result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
executor.shutdown();
}
}
Executor
框架?如何使用它來管理執行緒池?回答: Executor
框架是java.util.concurrent
包中的一個框架,用於簡化執行緒的管理和使用。它提供了一組介面和類來建立、管理和控制執行緒池,以及執行非同步任務。
可以通過Executors
類提供的工廠方法來建立不同型別的執行緒池,如newFixedThreadPool()
、newCachedThreadPool()
和newScheduledThreadPool()
等。
範例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorFrameworkExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3); // 建立一個固定大小的執行緒池
for (int i = 0; i < 10; i++) {
final int taskNum = i;
executor.execute(() -> {
System.out.println("Executing task " + taskNum);
});
}
executor.shutdown();
}
}
ScheduledExecutorService
?它用於什麼場景?回答: ScheduledExecutorService
是java.util.concurrent
包中的一個介面,它擴充套件了ExecutorService
介面,提供了一些用於排程定時任務的方法。它適用於需要在未來某個時間點執行任務,或以固定的時間間隔重複執行任務的場景。
通過ScheduledExecutorService
,可以建立週期性任務,如定時任務、心跳任務等。
範例:
import java.util.concurrent.Executors;
import java.util.concurrent.Scheduled
ExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorServiceExample {
public static void main(String[] args) {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); // 建立一個定時任務的執行緒池
Runnable task = () -> System.out.println("Scheduled task executed.");
// 延遲1秒後執行任務
executor.schedule(task, 1, TimeUnit.SECONDS);
// 延遲2秒後,每隔3秒重複執行任務
executor.scheduleAtFixedRate(task, 2, 3, TimeUnit.SECONDS);
// executor.shutdown();
}
}
ThreadLocal
?它的作用是什麼?有何注意事項?回答: ThreadLocal
是java.lang
包中的一個類,用於在每個執行緒中建立獨立的變數副本。每個執行緒可以通過ThreadLocal
獲取自己獨立的變數副本,從而避免了執行緒間的共用和競爭。
ThreadLocal
的主要作用是在多執行緒環境下為每個執行緒提供獨立的狀態,常見的使用場景包括執行緒池中的執行緒、Web應用中的使用者對談等。
注意事項:
ThreadLocal
使用後要確保呼叫remove()
方法來清除變數,以防止記憶體漏失。
謹慎使用ThreadLocal
,過多的使用可能會導致難以偵錯的問題。
範例:
public class ThreadLocalExample {
private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
threadLocal.set(1);
System.out.println("Thread 1: " + threadLocal.get());
});
Thread thread2 = new Thread(() -> {
threadLocal.set(2);
System.out.println("Thread 2: " + threadLocal.get());
});
thread1.start();
thread2.start();
}
}
Atomic
類提供了哪些原子操作?回答: 原子操作是不可被中斷的操作,要麼全部執行完成,要麼完全不執行,不會存在部分執行的情況。java.util.concurrent.atomic
包中提供了一系列Atomic
類,用於執行原子操作,保證多執行緒環境下的執行緒安全性。
一些常見的Atomic
類及其原子操作包括:
AtomicInteger
:整型原子操作,如addAndGet()、incrementAndGet()
等。
AtomicLong
:長整型原子操作,類似於AtomicInteger
。
AtomicBoolean
:布林型原子操作,如compareAndSet()
等。
AtomicReference
:參照型別原子操作,如compareAndSet()
等。
範例:
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerExample {
private static AtomicInteger counter = new AtomicInteger(0);
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.incrementAndGet();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.incrementAndGet();
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Counter value: " + counter.get());
}
}
Lock
介面?它與synchronized
關鍵字的區別是什麼?回答: Lock
介面是java.util.concurrent.locks
包中的一個介面,用於提供比synchronized
更細粒度的鎖機制。與synchronized
相比,Lock
介面提供了更多的功能,如可中斷鎖、可輪詢鎖、定時鎖等。
區別:
Lock
介面可以顯示地獲取和釋放鎖,而synchronized
是隱式的,由JVM自動管理。
Lock
介面提供了更多的靈活性和功能,如可重入鎖、公平鎖等。
範例:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
private static Lock lock = new ReentrantLock();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
lock.lock();
try {
System.out.println("Thread 1: Lock acquired.");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println("Thread 1: Lock released.");
}
});
Thread thread2 = new Thread(() -> {
lock.lock();
try {
System.out.println("Thread 2: Lock acquired.");
} finally {
lock.unlock();
System.out.println("Thread 2: Lock released.");
}
});
thread1.start();
thread2.start();
}
}
ReadWriteLock
?它如何在讀寫操作上提供更好的效能?回答: ReadWriteLock
是java.util.concurrent.locks
包中的一個介面,它提供了一種讀寫分離的鎖機制。與普通的鎖不同,ReadWriteLock
允許多個執行緒同時進行讀操作,但只允許一個執行緒進行寫操作。
在讀多寫少的場景下,使用ReadWriteLock
可以提供更好的效能,因為多個執行緒可以同時讀取資料,不需要互斥。只有在有寫操作時,才需要互斥。
範例:
import java.util.concurrent.locks
.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExample {
private static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static int value = 0;
public static void main(String[] args) {
Thread reader1 = new Thread(() -> {
readWriteLock.readLock().lock();
try {
System.out.println("Reader 1: Value is " + value);
} finally {
readWriteLock.readLock().unlock();
}
});
Thread reader2 = new Thread(() -> {
readWriteLock.readLock().lock();
try {
System.out.println("Reader 2: Value is " + value);
} finally {
readWriteLock.readLock().unlock();
}
});
Thread writer = new Thread(() -> {
readWriteLock.writeLock().lock();
try {
value = 42;
System.out.println("Writer: Value set to " + value);
} finally {
readWriteLock.writeLock().unlock();
}
});
reader1.start();
reader2.start();
writer.start();
}
}
Exchanger
?它的作用是什麼?回答: Exchanger
是java.util.concurrent
包中的一個同步工具,用於兩個執行緒之間交換資料。一個執行緒呼叫exchange()
方法將資料傳遞給另一個執行緒,當兩個執行緒都到達交換點時,資料交換完成。
Exchanger
可以用於解決生產者-消費者問題,或者任何需要兩個執行緒之間傳遞資料的場景。
範例:
import java.util.concurrent.Exchanger;
public class ExchangerExample {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
Thread thread1 = new Thread(() -> {
try {
String data = "Hello from Thread 1";
System.out.println("Thread 1 sending: " + data);
String receivedData = exchanger.exchange(data);
System.out.println("Thread 1 received: " + receivedData);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread thread2 = new Thread(() -> {
try {
String data = "Hello from Thread 2";
System.out.println("Thread 2 sending: " + data);
String receivedData = exchanger.exchange(data);
System.out.println("Thread 2 received: " + receivedData);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread1.start();
thread2.start();
}
}
Semaphore
?它的作用是什麼?回答: Semaphore
是java.util.concurrent
包中的一個計數號誌,用於控制同時存取某個資源的執行緒數量。它適用於限制同時存取某一資源的執行緒數量,從而避免過多的並行存取。
Semaphore
通過呼叫acquire()
來獲取許可證,表示可以存取資源,通過呼叫release()
來釋放許可證,表示釋放資源。Semaphore
的內部計數器可以控制同時獲取許可證的執行緒數量。
範例:
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(2); // 允許兩個執行緒同時存取
Thread thread1 = new Thread(() -> {
try {
semaphore.acquire();
System.out.println("Thread 1 acquired a permit.");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
System.out.println("Thread 1 released a permit.");
}
});
Thread thread2 = new Thread(() -> {
try {
semaphore.acquire();
System.out.println("Thread 2 acquired a permit.");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
System.out.println("Thread 2 released a permit.");
}
});
thread1.start();
thread2.start();
}
}
BlockingQueue
?它的作用是什麼?舉例說明一個使用場景。回答: BlockingQueue
是java.util.concurrent
包中的一個介面,表示一個支援阻塞的佇列。它的主要作用是實現執行緒間的資料傳遞和共同作業,特別適用於解決生產者-消費者問題。
BlockingQueue
可以在佇列為空時阻塞等待元素的到來,或在佇列已滿時阻塞等待佇列有空間。它提供了一種簡單的方式來實現多個執行緒之間的資料交換。
範例:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class BlockingQueueExample {
public static void main(String[] args) {
BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
Thread producer = new Thread(() -> {
try {
queue.put("Item 1");
queue.put("Item 2");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread consumer = new Thread(() -> {
try {
String item1 = queue.take();
String item2 = queue.take();
System.out.println("Consumed: " + item1 + ", " + item2);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
producer.start();
consumer.start();
}
}
CompletableFuture
?它的作用是什麼?舉例說明一個使用場景。回答: CompletableFuture
是java.util.concurrent
包中的一個類,用於支援非同步程式設計和函數語言程式設計風格。它可以用於序列和並行地執行非同步任務,並在任務完成後執行一些操作。
CompletableFuture
的作用包括:
非同步執行任務,提高程式的響應性。
支援函數語言程式設計風格,可以鏈式地定義一系列操作。
支援任務的組合、聚合等複雜操作。
範例:
import java.util.concurrent.CompletableFuture;
public class CompletableFutureExample {
public static void main(String[] args) {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println("Executing task asynchronously...");
return 42;
});
future.thenAccept(result -> {
System.out.println("Result: " + result);
});
// 等待任務完成
future.join();
}
}
StampedLock
?它的作用是什麼?回答: StampedLock
是java.util.concurrent.locks
包中的一個類,提供了一種樂觀讀、寫鎖的機制,用於優化讀多寫少的場景。
StampedLock
的作用是在並行讀操作時使用樂觀鎖(tryOptimisticRead()
),避免了不必要的阻塞,提高了讀操作的效能。當需要進行寫操作時,可以嘗試升級為寫鎖。
範例:
import java.util.concurrent.locks.StampedLock;
public class StampedLockExample {
private static StampedLock lock = new StampedLock();
private static int value = 0;
public static void main(String[] args) {
Runnable readTask = () -> {
long stamp = lock.tryOptimisticRead();
int currentValue = value;
if (!lock.validate(stamp)) {
stamp = lock.readLock();
try {
currentValue = value;
} finally {
lock.unlockRead(stamp);
}
}
System.out.println("Read: " + currentValue);
};
Runnable writeTask = () -> {
long stamp = lock.writeLock();
try {
value++;
System.out.println("Write: " + value);
} finally {
lock.unlockWrite(stamp);
}
};
Thread reader1 = new Thread(readTask);
Thread reader2 = new Thread(readTask);
Thread writer1 = new Thread(writeTask);
Thread reader3 = new Thread(readTask);
reader1.start();
reader2.start();
writer1.start();
reader3.start();
}
}
ForkJoinPool
?它適用於什麼場景?回答: ForkJoinPool
是java.util.concurrent
包中的一個執行緒池實現,特別適用於解決分治問題(Divide and Conquer)的平行計算。它通過將大任務拆分為小任務,分配給執行緒池中的執行緒來進行並行計
算,然後將結果進行合併。
ForkJoinPool
適用於需要將問題分解為多個子問題並並行求解的情況,比如遞迴、歸併排序、MapReduce等演演算法。
範例:
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.ForkJoinPool;
public class ForkJoinPoolExample {
static class RecursiveFactorialTask extends RecursiveTask<Long> {
private final int start;
private final int end;
RecursiveFactorialTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if (end - start <= 5) {
long result = 1;
for (int i = start; i <= end; i++) {
result *= i;
}
return result;
} else {
int middle = (start + end) / 2;
RecursiveFactorialTask leftTask = new RecursiveFactorialTask(start, middle);
RecursiveFactorialTask rightTask = new RecursiveFactorialTask(middle + 1, end);
leftTask.fork();
rightTask.fork();
return leftTask.join() * rightTask.join();
}
}
}
public static void main(String[] args) {
ForkJoinPool forkJoinPool = new ForkJoinPool();
RecursiveFactorialTask task = new RecursiveFactorialTask(1, 10);
long result = forkJoinPool.invoke(task);
System.out.println("Factorial result: " + result);
}
}
CyclicBarrier
?它的作用是什麼?回答: CyclicBarrier
是java.util.concurrent
包中的一個同步工具,用於等待多個執行緒都達到一個共同的屏障點,然後再一起繼續執行。它適用於多執行緒任務之間的同步共同作業,等待所有執行緒都完成某個階段後再繼續下一階段。
CyclicBarrier
可以被重複使用,每當所有等待執行緒都到達屏障點後,它會自動重置,可以繼續下一輪的等待和執行。
範例:
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("All threads reached the barrier. Continuing...");
});
Runnable task = () -> {
try {
System.out.println(Thread.currentThread().getName() + " is waiting at the barrier.");
barrier.await();
System.out.println(Thread.currentThread().getName() + " passed the barrier.");
} catch (Exception e) {
e.printStackTrace();
}
};
Thread thread1 = new Thread(task, "Thread 1");
Thread thread2 = new Thread(task, "Thread 2");
Thread thread3 = new Thread(task, "Thread 3");
thread1.start();
thread2.start();
thread3.start();
}
}
CountDownLatch
?它的作用是什麼?回答: CountDownLatch
是java.util.concurrent
包中的一個同步工具,用於等待多個執行緒都完成某個任務後再繼續執行。它適用於一個執行緒等待其他多個執行緒的場景,常見於主執行緒等待子執行緒完成任務。
CountDownLatch
內部維護一個計數器,每個執行緒完成任務時會減小計數器的值,當計數器為0時,等待的執行緒可以繼續執行。
範例:
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(3);
Runnable task = () -> {
System.out.println(Thread.currentThread().getName() + " is working.");
latch.countDown();
};
Thread thread1 = new Thread(task, "Thread 1");
Thread thread2 = new Thread(task, "Thread 2");
Thread thread3 = new Thread(task, "Thread 3");
thread1.start();
thread2.start();
thread3.start();
try {
latch.await(); // 等待計數器歸零
System.out.println("All threads have completed their tasks. Continuing...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Phaser
?它的作用是什麼?回答: Phaser
是java.util.concurrent
包中的一個同步工具,用於協調多個執行緒的階段性任務。它提供了類似於CyclicBarrier
和CountDownLatch
的功能,但更加靈活。
Phaser
支援多個階段,每個階段可以包含多個執行緒。在每個階段結束時,所有執行緒都會等待,直到所有執行緒都到達該階段才會繼續執行。
範例:
import java.util.concurrent.Phaser;
public class PhaserExample {
public static void main(String[] args) {
Phaser phaser = new Phaser(3); // 3個執行緒參與
Runnable task = () -> {
System.out.println(Thread.currentThread().getName() + " is working in phase " + phaser.getPhase());
phaser.arriveAndAwaitAdvance(); // 等待其他執行緒完成
System.out.println(Thread.currentThread().getName() + " completed phase " + phaser.getPhase());
};
Thread thread1 = new Thread(task, "Thread 1");
Thread thread2 = new Thread(task, "Thread 2");
Thread thread3 = new Thread(task, "Thread 3");
thread1.start();
thread2.start();
thread3.start();
phaser.arriveAndAwaitAdvance(); // 等待所有執行緒完成第一階段
System.out.println("All threads completed phase 0. Proceeding to the next phase.");
phaser.arriveAndAwaitAdvance(); // 等待所有執行緒完成第二階段
System.out.println("All threads completed phase 1. Exiting.");
}
}
BlockingDeque
?它與BlockingQueue
有何不同?回答: BlockingDeque
是java.util.concurrent
包中的一個介面,表示一個雙端阻塞佇列,即可以在隊頭和隊尾進行插入和移除操作。與BlockingQueue
相比,BlockingDeque
支援更豐富的操作,例如可以在隊頭和隊尾插入和移除元素,從隊頭和隊尾獲取元素等。
BlockingDeque
的實現類包括LinkedBlockingDeque
和LinkedBlockingDeque
,它們可以用於實現多生產者-多消費者的並行場景。
TransferQueue
?它的作用是什麼?回答: TransferQueue
是java.util.concurrent
包中的一個介面,表示一個支援直接傳輸的阻塞佇列。它是BlockingQueue
的擴充套件,提供了更豐富的操作,其中最顯著的是transfer()
方法,該方法可以直接將元素傳遞給等待的消費者執行緒。
TransferQueue
適用於一種特殊的生產者-消費者場景,其中生產者不僅可以將元素插入佇列,還可以將元素直接傳遞給等待的消費者。
範例:
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.TransferQueue;
public class TransferQueueExample {
public static void main(String[] args) {
TransferQueue<String> transferQueue = new LinkedTransferQueue<>();
Thread producer = new Thread(() -> {
try {
transferQueue.transfer("Item 1");
System.out.println("Item 1 transferred.");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread consumer = new Thread(() -> {
try {
String item = transferQueue.take();
System.out.println("Item received: " + item);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
producer.start();
consumer.start();
}
}
ScheduledExecutorService
?它的作用是什麼?回答: ScheduledExecutorService
是java.util.concurrent
包中的一個介面,用於支援按計劃執行任務,即在指定的時間點或以固定的時間間隔執行任務。它提供了一種簡單的方式來實現定時任務。
ScheduledExecutorService
可以執行定時任務,如在一定延遲後執行一次,或者按照固定的時間間隔週期性地執行任務。
範例:
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorServiceExample {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
Runnable task = () -> {
System.out.println("Task executed at: " + System.currentTimeMillis());
};
// 在5秒後執行任務
scheduledExecutorService.schedule(task, 5, TimeUnit.SECONDS);
// 每隔2秒執行任務
scheduledExecutorService.scheduleAtFixedRate(task, 0, 2, TimeUnit.SECONDS);
}
}
ForkJoinTask
?它的作用是什麼?回答: ForkJoinTask
是java.util.concurrent
包中的一個抽象類,用於表示可以被ForkJoinPool
並行執行的任務。它是使用Fork-Join
框架的基礎。
ForkJoinTask
的作用是將一個大的任務分割成更小的子任務,然後遞迴地並行執行這些子任務,最終將子任務的結果合併起來。它適用於需要並行處理的遞迴型問題,如歸併排序、斐波那契數列等。
範例:
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.ForkJoinPool;
public class ForkJoinTaskExample {
static class RecursiveFactorialTask extends RecursiveTask<Long> {
private final int n;
RecursiveFactorialTask(int n) {
this.n = n;
}
@Override
protected Long compute() {
if (n <= 1) {
return 1L;
} else {
RecursiveFactorialTask subtask = new RecursiveFactorialTask(n - 1);
subtask.fork();
return n * subtask.join();
}
}
}
public static void main(String[] args) {
ForkJoinPool forkJoinPool = new ForkJoinPool();
RecursiveFactorialTask task = new RecursiveFactorialTask(5);
long result = forkJoinPool.invoke(task);
System.out.println("Factorial result: " + result);
}
}
CompletableFuture
的組合操作?回答: CompletableFuture
支援一系列的組合操作,允許對非同步任務的結果進行鏈式處理。這些組合操作包括:
thenApply(Function<T, U> fn)
: 對任務的結果進行對映轉換。thenCompose(Function<T, CompletionStage<U>> fn)
: 將前一個任務的結果傳遞給下一個任務。thenCombine(CompletionStage<U> other, BiFunction<T, U, V> fn)
: 合併兩個任務的結果。thenAccept(Consumer<T> action)
: 對任務的結果進行消費。thenRun(Runnable action)
: 在任務完成後執行一個操作。這些組合操作可以通過鏈式呼叫來序列執行一系列操作。
範例:
import java.util.concurrent.CompletableFuture;
public class CompletableFutureCompositionExample {
public static void main(String[] args) {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 10)
.thenApply(result -> result * 2)
.thenCompose(result -> CompletableFuture.supplyAsync(() -> result + 3))
.thenCombine(CompletableFuture.completedFuture(5), (result1, result2) -> result1 + result2)
.thenAccept(result -> System.out.println("Final result: " + result))
.thenRun(() -> System.out.println("All operations completed."));
future.join();
}
}
ForkJoinTask
的工作竊取機制?回答: ForkJoinTask
通過工作竊取(Work-Stealing)機制來實現任務的負載均衡。在ForkJoinPool
中,每個執行緒都維護一個雙端佇列,存放自己的任務。當一個執行緒完成自己佇列中的任務後,它可以從其他執行緒的佇列中竊取任務執行,以保持執行緒的充分利用。
工作竊取機制能夠在某些情況下避免執行緒因為某個任務的阻塞而空閒,從而提高了任務的並行性和效率。
ConcurrentHashMap
與HashTable
之間的區別是什麼?回答: ConcurrentHashMap
和HashTable
都是用於實現執行緒安全的雜湊表,但它們之間有一些關鍵的區別:
ConcurrentHashMap
支援更高的並行度,它將雜湊表分割為多個段(Segment),每個段上可以獨立加鎖,從而允許多個執行緒同時存取不同的段,降低了鎖的競爭。
鎖粒度: HashTable
在進行操作時需要鎖住整個資料結構,而ConcurrentHashMap
只需要鎖住某個段,使得並行性更高。
Null值: HashTable
不允許鍵或值為null
,而ConcurrentHashMap
允許鍵和值都為null
。
迭代: ConcurrentHashMap
的迭代器是弱一致性的,可能會反映之前或之後的更新操作。而HashTable
的迭代是強一致性的。
總的來說,如果需要更好的並行效能和更高的靈活性,通常會優先選擇使用ConcurrentHashMap
,而不是HashTable
。
Exchanger
?它的作用是什麼?回答: Exchanger
是java.util.concurrent
包中的一個同步工具,用於在兩個執行緒之間交換資料。每個執行緒呼叫exchange()
方法後會阻塞,直到另一個執行緒也呼叫了相同的exchange()
方法,然後兩個執行緒之間交換資料。
Exchanger
適用於需要在兩個執行緒之間傳遞資料的場景,如一個執行緒生成資料,另一個執行緒處理資料。
範例:
import java.util.concurrent.Exchanger;
public class ExchangerExample {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
Thread producer = new Thread(() -> {
try {
String data = "Hello from producer!";
System.out.println("Producer is sending: " + data);
exchanger.exchange(data);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread consumer = new Thread(() -> {
try {
String receivedData = exchanger.exchange(null);
System.out.println("Consumer received: " + receivedData);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
producer.start();
consumer.start();
}
}
BlockingQueue
與Exchanger
之間的區別是什麼?回答: BlockingQueue
和Exchanger
都是用於執行緒間資料傳遞和共同作業的同步工具,但它們之間有一些關鍵的區別:
資料傳遞方式: BlockingQueue
通過佇列的方式實現資料傳遞,生產者將資料放入佇列,消費者從佇列中取出資料。而Exchanger
是通過兩個執行緒之間直接交換資料。
共同作業方式: BlockingQueue
適用於多生產者-多消費者場景,允許多個執行緒並行地插入和移除資料。Exchanger
適用於兩個執行緒之間的資料交換。
阻塞機制: BlockingQueue
中的操作(如put()
和take()
)會阻塞等待佇列的狀態發生變化。Exchanger
中的操作(如exchange()
)會阻塞等待另一個執行緒到達。
總的來說,如果需要多個生產者和消費者之間進行資料交換,可以選擇使用BlockingQueue
。如果只需要兩個執行緒之間直接交換資料,可以選擇使用Exchanger
。
Semaphore
的公平性?回答: Semaphore
提供了兩種模式:公平模式和非公平模式。在公平模式下,Semaphore
會按照請求許可的順序分配許可,即等待時間最長的執行緒會先獲得許可。在非公平模式下,許可會分配給當前可用的執行緒,不考慮等待的順序。
在公平模式下,雖然保證了公平性,但可能會導致執行緒上下文切換的頻繁發生,降低了效能。在非公平模式下,可能會出現等待時間較短的執行緒獲取許可的情況,但效能可能會更好。
可以使用Semaphore
的構造方法指定公平或非公平模式,預設情況下是非公平模式。
ConcurrentHashMap
如何保證執行緒安全?回答: ConcurrentHashMap
使用了多種技術來保證執行緒安全:
分段鎖: ConcurrentHashMap
將內部的雜湊表分成多個段(Segment),每個段上都有一個鎖。不同的段可以在不同的執行緒上互相獨立操作,減小了鎖的粒度,提高了並行效能。
CAS操作: 在某些情況下,ConcurrentHashMap
使用了CAS(Compare and Swap)操作,避免了使用傳統的鎖機制,提高了效能。
同步控制: ConcurrentHashMap
使用了適當的同步控制來保證不同操作的原子性,如putIfAbsent()
等。
可伸縮性: ConcurrentHashMap
支援並行度的調整,可以通過調整Segment
的數量來適應不同的並行級別。
以上這些技術的結合使得ConcurrentHashMap
能夠在高並行情況下保證執行緒安全。
StampedLock
的樂觀讀?回答: StampedLock
的樂觀讀是一種特殊的讀操作,它不會阻塞其他執行緒的寫操作,但也不會提供強一致性的保證。在樂觀讀期間,如果有其他執行緒執行了寫操作,樂觀讀會失敗。
StampedLock
的樂觀讀通過呼叫tryOptimisticRead()
方法開始,它會返回一個標記(stamp)。在樂觀讀期間,如果沒有寫操作發生,就可以使用這個標記來獲取資料。如果樂觀讀之後要進行進一步的操作,可以呼叫validate(stamp)
來檢查標記是否仍然有效。
樂觀讀適用於讀多寫少的情況,可以提高讀操作的效能。
Semaphore
?它的作用是什麼?回答: Semaphore
是java.util.concurrent
包中的一個同步工具,用於控制同時存取某個資源的執行緒數量。它通過維護一個許可數來限制執行緒的並行存取。
Semaphore
可以用於限制同時執行某個特定操作的執行緒數量,或者控制同時存取某個資源(如資料庫連線、檔案)的執行緒數量。
範例:
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3); // 限制同時存取的執行緒數為3
Runnable task = () -> {
try {
semaphore.acquire(); // 獲取許可
System.out.println(Thread.currentThread().getName() + " is performing the task.");
Thread.sleep(2000); // 模擬任務執行
System.out.println(Thread.currentThread().getName() + " completed the task.");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 釋放許可
}
};
Thread thread1 = new Thread(task, "Thread 1");
Thread thread2 = new Thread(task, "Thread 2");
Thread thread3 = new Thread(task, "Thread 3");
thread1.start();
thread2.start();
thread3.start();
}
}
ThreadLocal
?它的作用是什麼?回答: ThreadLocal
是java.lang
包中的一個類,用於在每個執行緒中儲存資料副本。每個執行緒都可以獨立地存取自己的資料副本,互不影響其他執行緒的資料。
ThreadLocal
可以用於實現執行緒範圍內的資料共用,每個執行緒可以在其中儲存自己的資料,不需要顯式的同步控制。它適用於需要線上程之間隔離資料的情況,如儲存使用者對談資訊、資料庫連線等。
範例:
public class ThreadLocalExample {
private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
public static void main(String[] args) {
Runnable task = () -> {
int value = threadLocal.get(); // 獲取執行緒的資料副本
System.out.println(Thread.currentThread().getName() + " has value: " + value);
threadLocal.set(value + 1); // 修改執行緒的資料副本
};
Thread thread1 = new Thread(task, "Thread 1");
Thread thread2 = new Thread(task, "Thread 2");
thread1.start();
thread2.start();
}
}
CompletableFuture
如何處理異常?回答: CompletableFuture
可以通過exceptionally
和handle
方法來處理異常情況。
exceptionally
方法:在發生異常時,可以通過exceptionally
方法提供一個處理常式,返回一個預設值或恢復操作。該處理常式只會在異常情況下被呼叫。
handle
方法:handle
方法結合了正常結果和異常情況的處理。它接收一個BiFunction
,無論是正常結果還是異常,都會被傳遞給這個函數。
範例:
import java.util.concurrent.CompletableFuture;
public class CompletableFutureExceptionHandling {
public static void main(String[] args) {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
if (Math.random() < 0.5) {
throw new RuntimeException("Task failed!");
}
return 42;
});
CompletableFuture<Integer> resultFuture = future
.exceptionally(ex -> {
System.out.println("Exception occurred: " + ex.getMessage());
return -1; // 返回預設值
})
.handle((result, ex) -> {
if (ex != null) {
System.out.println("Handled exception: " + ex.getMessage());
return -1;
}
return result;
});
resultFuture.thenAccept(result -> System.out.println("Final result: " + result));
}
}
StampedLock
的樂觀讀和悲觀讀有什麼區別?回答: StampedLock
支援兩種讀模式:樂觀讀(Optimistic Read)和悲觀讀(Pessimistic Read)。
樂觀讀: 樂觀讀是一種無鎖的讀操作,使用tryOptimisticRead()
方法可以獲取一個標記(stamp),然後進行讀操作。在樂觀讀期間,如果沒有寫操作發生,讀取的資料是有效的。如果後續要對資料進行寫操作,需要使用validate(stamp)
方法來驗證標記是否仍然有效。
悲觀讀: 悲觀讀是一種使用讀鎖的讀操作,使用readLock()
方法來獲取讀鎖,保證在讀操作期間不會被寫操作所影響。
樂觀讀適用於讀多寫少的情況,悲觀讀適用於讀寫並行較高的情況。
CountDownLatch
與CyclicBarrier
之間的區別是什麼?回答: CountDownLatch
和CyclicBarrier
都是用於協調多個執行緒之間的同步,但它們之間有一些關鍵的區別:
使用場景: CountDownLatch
用於等待多個執行緒完成某個任務,然後繼續執行。CyclicBarrier
用於等待多個執行緒都達到一個共同的屏障點,然後再一起繼續執行。
重用性: CountDownLatch
的計數器只能使用一次,一旦計數器歸零,就不能再使用。CyclicBarrier
可以被重複使用,每次都會自動重置。
等待機制: CountDownLatch
使用await()
方法等待計
數器歸零。CyclicBarrier
使用await()
方法等待所有執行緒到達屏障點。
CountDownLatch
的計數器數量固定。CyclicBarrier
的屏障點數量由使用者指定。總的來說,如果需要等待多個執行緒都完成某個任務後再繼續執行,可以選擇使用CountDownLatch
。如果需要等待多個執行緒都達到一個共同的屏障點再一起繼續執行,可以選擇使用CyclicBarrier
。
Semaphore
和ReentrantLock
之間的區別是什麼?回答: Semaphore
和ReentrantLock
都是java.util.concurrent
包中用於執行緒同步的工具,但它們之間有一些區別:
用途: Semaphore
用於控制同時存取某個資源的執行緒數量,而ReentrantLock
用於提供獨佔鎖功能,即只有一個執行緒可以獲取鎖並存取受保護的資源。
鎖的型別: Semaphore
不是一種鎖,而是一種號誌機制。ReentrantLock
是一種顯式的獨佔鎖。
並行度: Semaphore
可以同時允許多個執行緒存取受保護資源,具有更高的並行度。ReentrantLock
在同一時刻只允許一個執行緒存取受保護資源。
阻塞機制: Semaphore
使用許可機制來控制存取,當沒有許可時,執行緒會阻塞等待。ReentrantLock
使用可重入鎖,執行緒可以重複獲得鎖,但需要相應數量的解鎖操作。
應用場景: Semaphore
適用於資源池管理、限流等場景。ReentrantLock
適用於更加複雜的同步需求,可以控制鎖的獲取和釋放,提供更多靈活性。
Semaphore
的公平性與非公平性有什麼區別?回答: Semaphore
可以使用公平模式和非公平模式。
公平模式: 在公平模式下,Semaphore
會按照執行緒請求許可的順序分配許可,即等待時間最長的執行緒會先獲得許可。公平模式保證了執行緒的公平競爭,但可能會導致執行緒上下文切換頻繁。
非公平模式: 在非公平模式下,Semaphore
不會考慮執行緒的等待時間,許可會分配給當前可用的執行緒。非公平模式可能會導致等待時間較短的執行緒優先獲得許可,但效能可能會更好。
可以通過Semaphore
的構造方法來指定使用公平模式還是非公平模式,預設情況下是非公平模式。
ReentrantReadWriteLock
是什麼?它的作用是什麼?回答: ReentrantReadWriteLock
是java.util.concurrent
包中的一個鎖實現,用於解決讀寫鎖問題。它允許多個執行緒同時進行讀操作,但在進行寫操作時只允許一個執行緒。
ReentrantReadWriteLock
由一個讀鎖和一個寫鎖組成。讀鎖允許多個執行緒同時獲得鎖進行讀操作,寫鎖只允許一個執行緒獲得鎖進行寫操作。
ReentrantReadWriteLock
適用於讀多寫少的場景,可以提高並行效能。
範例:
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReentrantReadWriteLockExample {
private static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static int value = 0;
public static void main(String[] args) {
Runnable readTask = () -> {
readWriteLock.readLock().lock();
try {
System.out.println("Read value: " + value);
} finally {
readWriteLock.readLock().unlock();
}
};
Runnable writeTask = () -> {
readWriteLock.writeLock().lock();
try {
value++;
System.out.println("Write value: " + value);
} finally {
readWriteLock.writeLock().unlock();
}
};
Thread readThread1 = new Thread(readTask);
Thread readThread2 = new Thread(readTask);
Thread writeThread = new Thread(writeTask);
readThread1.start();
readThread2.start();
writeThread.start();
}
}
Phaser
和CyclicBarrier
之間的區別是什麼?回答: Phaser
和CyclicBarrier
都是用於多執行緒之間的協調和同步,但它們之間有一些區別:
屏障點數量: CyclicBarrier
的屏障點數量是在建立時指定的,且在初始化之後不能更改。而Phaser
的屏障點數量可以在任何時候進行動態修改。
註冊執行緒數: Phaser
允許動態註冊和登出參與者執行緒,可以動態地控制協調的執行緒數量。而CyclicBarrier
一旦建立,執行緒數量是固定的。
階段(Phase): Phaser
支援多個階段,每個階段可以有不同的參與者數量。每次進入新階段時,Phaser
會重新計數。
回撥功能: Phaser
支援在每個階段的入口和出口設定回撥函數,用於執行特定操作。
總的來說,如果需要更靈活地控制執行緒數量和階段,以及支援動態的參與者註冊和登出,可以選擇使用Phaser
。如果只需要等待多個執行緒都達到一個共同的屏障點再一起繼續執行,可以選擇使用CyclicBarrier
。
Exchanger
和TransferQueue
之間的區別是什麼?回答: Exchanger
和TransferQueue
都是用於執行緒之間的資料交換,但它們之間有一些區別
:
交換方式: Exchanger
是一種簡單的同步工具,只允許兩個執行緒之間交換資料。TransferQueue
是一個更高階的介面,支援多個執行緒之間的資料傳遞。
用途: Exchanger
用於在兩個執行緒之間交換資料,每個執行緒等待對方。TransferQueue
用於實現生產者-消費者模型,支援多個生產者和消費者,可以在佇列中傳遞資料。
特性: Exchanger
只提供資料交換功能,不涉及其他操作。TransferQueue
提供了更豐富的佇列操作,如put()
、take()
等。
實現: Exchanger
的實現是基於Lock
和Condition
等基本同步工具。TransferQueue
的實現通常基於連結串列等資料結構,同時也使用了Lock
等同步機制。
總的來說,如果需要簡單的兩個執行緒之間的資料交換,可以選擇使用Exchanger
。如果需要實現更復雜的生產者-消費者模型,可以選擇使用TransferQueue
的實現類,如LinkedTransferQueue
。
BlockingQueue
和TransferQueue
之間的區別是什麼?回答: BlockingQueue
和TransferQueue
都是java.util.concurrent
包中的介面,用於實現生產者-消費者模型,但它們之間有一些區別:
資料傳遞方式: BlockingQueue
使用佇列的方式進行資料傳遞,生產者將資料放入佇列,消費者從佇列中取出資料。TransferQueue
也使用佇列的方式進行資料傳遞,但具有更多特性,如阻塞等待生產者或消費者就緒。
特性: BlockingQueue
提供了多種阻塞等待的方法,如put()
、take()
等。TransferQueue
在BlockingQueue
的基礎上增加了更豐富的特性,如tryTransfer()
、hasWaitingConsumer()
等,使得生產者-消費者模型更靈活。
等待機制: BlockingQueue
中的操作(如put()
和take()
)會阻塞等待佇列的狀態發生變化。TransferQueue
中的操作(如transfer()
和take()
)也會阻塞等待,但可以等待生產者或消費者就緒。
總的來說,TransferQueue
是BlockingQueue
的擴充套件,提供了更豐富的特性,適用於更靈活的生產者-消費者模型。
CompletableFuture
和Future
之間的區別是什麼?回答: CompletableFuture
和Future
都是用於非同步程式設計的介面,但它們之間有一些區別:
是否可編排: CompletableFuture
支援編排多個非同步操作,可以通過一系列的方法鏈來組合多個操作,使得非同步操作更具可讀性。Future
不支援直接的方法鏈編排。
回撥機制: CompletableFuture
支援新增回撥函數,可以在非同步操作完成後執行指定的操作。Future
本身不支援回撥機制,但可以通過輪詢的方式來檢查非同步操作的完成狀態。
例外處理: CompletableFuture
支援更靈活的例外處理,可以通過handle()
、exceptionally()
等方法來處理異常情況。Future
的例外處理相對有限,需要在呼叫get()
方法時捕獲ExecutionException
。
非同步操作結果: CompletableFuture
的方法可以返回新的CompletableFuture
,使得非同步操作的結果可以被後續的操作使用。Future
的結果通常需要通過get()
方法獲取,且不支援鏈式操作。
總的來說,CompletableFuture
提供了更強大的非同步程式設計能力,更靈活的例外處理和編排機制,使得非同步操作更加簡潔和可讀。
Semaphore
和Mutex
之間有什麼區別?回答: Semaphore
和Mutex
(互斥鎖)都是用於實現執行緒同步的機制,但它們之間有一些區別:
用途: Semaphore
用於控制同時存取某個資源的執行緒數量,可以允許多個執行緒同時存取。Mutex
用於保護臨界區資源,同時只允許一個執行緒存取。
執行緒數: Semaphore
可以同時允許多個執行緒存取受保護資源,其許可數可以設定。Mutex
只允許一個執行緒獲得鎖。
操作: Semaphore
的主要操作是acquire()
和release()
,分別用於獲取和釋放許可。Mutex
的操作是獲取和釋放鎖,通常使用lock()
和unlock()
。
應用場景: Semaphore
適用於資源池管理、限流等場景,需要控制並行存取的執行緒數量。Mutex
適用於需要保護臨界區資源,防止並行存取造成資料不一致的場景。
總的來說,Semaphore
更多地用於控制並行存取的執行緒數量,而Mutex
更多地用於保護共用資源的完整性。
ReadWriteLock
和StampedLock
之間的區別是什麼?回答: ReadWriteLock
和StampedLock
都是java.util.concurrent
包中用於實現讀寫鎖的機制,但它們之間有一些區別:
支援的模式: ReadWriteLock
支援經典的讀鎖和寫鎖模式,允許多個執行緒同時獲得讀鎖,但在寫鎖模式下只允許一個執行緒獲得鎖。StampedLock
除了支援讀鎖和寫鎖模式外,還支援樂觀讀模式。
樂觀讀: StampedLock
支援樂觀讀,允許在讀鎖的基礎上進行無鎖讀操作,但可能會失敗。ReadWriteLock
不支援樂觀讀。
效能: StampedLock
的樂觀讀操作具有更低的開銷,適用於讀多寫少的情況。ReadWriteLock
適用於讀寫操作相對均衡的情況。
應用場景: ReadWriteLock
適用於需要高並行的讀操作場景,如快取。StampedLock
適用於讀多寫少且對效能有較高要求的場景,可以使用樂觀讀提高效能。
總的來說,如果需要更細粒度的讀寫控制和支援樂觀讀模式,可以選擇使用StampedLock
。如果只需要傳統的讀寫鎖模式,可以選擇使用ReadWriteLock
。
BlockingQueue
和SynchronousQueue
之間的區別是什麼?回答: BlockingQueue
和SynchronousQueue
都是java.util.concurrent
包中的佇列,但它們之間有一些區別:
佇列特性: BlockingQueue
是一個允許在佇列為空或滿時進行阻塞等待的佇列,支援多個生產者和消費者。SynchronousQueue
是一個特殊的佇列,它不儲存元素,每個插入操作必須等待一個對應的刪除操作,反之亦然。
儲存元素: BlockingQueue
可以儲存多個元素,具有一定的容量。SynchronousQueue
不儲存元素,每次插入操作需要等待相應的刪除操作。
用途: BlockingQueue
適用於生產者-消費者模型,允許在佇列滿或空時進行合理的等待。SynchronousQueue
適用於一對一的執行緒通訊,生產者必須等待消費者消費。
效能: SynchronousQueue
的效能相對較高,因為它不需要儲存元素,只是進行傳遞。
總的來說,BlockingQueue
適用於多生產者-多消費者的場景,需要在佇列滿或空時進行等待。SynchronousQueue
適用於一對一的執行緒通訊,具有更高的效能。
CopyOnWriteArrayList
和ArrayList
之間的區別是什麼?回答: CopyOnWriteArrayList
和ArrayList
都是java.util.concurrent
包中的列表,但它們之間有一些區別:
並行性: ArrayList
不是執行緒安全的,多個執行緒同時進行讀寫操作可能導致資料不一致。CopyOnWriteArrayList
是執行緒安全的,可以在多執行緒環境下進行讀寫操作。
寫操作開銷: ArrayList
在寫操作(如新增、刪除元素)時需要進行顯式的同步控制,可能引起執行緒阻塞。CopyOnWriteArrayList
通過複製整個資料結構來實現寫操作,不會阻塞正在進行的讀操作,但會引起寫操作的開銷。
迭代器: ArrayList
的迭代器不支援並行修改,可能會丟擲ConcurrentModificationException
異常。CopyOnWriteArrayList
的迭代器支援並行修改,可以在迭代的同時進行修改。
適用場景: ArrayList
適用於單執行緒環境或唯讀操作的多執行緒環境。CopyOnWriteArrayList
適用於讀多寫少的多執行緒環境,適合在遍歷操作頻繁、寫操作較少的情況下使用。
總的來說,如果需要在多執行緒環境中進行讀寫操作,可以選擇使用CopyOnWriteArrayList
,以保證執行緒安全性。如果在單執行緒環境或唯讀操作的多執行緒環境下使用,可以選擇使用ArrayList
。
ForkJoinPool
是什麼?它的作用是什麼?回答: ForkJoinPool
是java.util.concurrent
包中的一個執行緒池實現,專門用於支援分治任務的並行處理。它的主要作用是高效地執行可以被拆分為子任務並行執行的任務,將大任務拆分為小任務,然後將子任務的結果合併。
ForkJoinPool
使用工作竊取(Work-Stealing)演演算法,即空閒執行緒從其他執行緒的任務佇列中竊取任務來執行。這可以減少執行緒等待時間,提高並行處理效率。
ForkJoinPool
通常用於解決遞迴、分治、MapReduce等問題,如並行排序、矩陣乘法等。
範例:
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.ForkJoinPool;
public class ForkJoinPoolExample {
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int sum = pool.invoke(new SumTask(array, 0, array.length - 1));
System.out.println("Sum: " + sum);
pool.shutdown();
}
}
class SumTask extends RecursiveTask<Integer> {
private int[] array;
private int start;
private int end;
public SumTask(int[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
if (end - start <= 2) {
int sum = 0;
for (int i = start; i <= end; i++) {
sum += array[i];
}
return sum;
} else {
int mid = (start + end) / 2;
SumTask left = new SumTask(array, start, mid);
SumTask right = new SumTask(array, mid + 1, end);
left.fork();
int rightResult = right.compute();
int leftResult = left.join();
return leftResult + rightResult;
}
}
}
在上面的範例中,我們使用ForkJoinPool
執行了一個求和任務,將大陣列拆分為子任務並行執行,然後合併結果。這展示了ForkJoinPool
的用法和分治任務的處理方式。
CompletableFuture
的thenCompose
和thenCombine
有什麼區別?回答: thenCompose
和thenCombine
都是CompletableFuture
的組合方法,用於處理非同步操作的結果。它們之間的區別
在於:
thenCompose
: thenCompose
方法用於將一個非同步操作的結果傳遞給另一個非同步操作,並返回一個新的CompletableFuture
。第二個操作的返回值是一個CompletionStage
,通過thenCompose
可以將兩個操作串聯起來,實現鏈式的非同步操作。
thenCombine
: thenCombine
方法用於組合兩個獨立的非同步操作的結果,然後對這兩個結果進行處理,並返回一個新的CompletableFuture
。它接受一個BiFunction
引數,用於將兩個結果進行合併。
範例:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureCombineExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 2);
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 3);
CompletableFuture<Integer> combinedFuture = future1.thenCombine(future2, (result1, result2) -> result1 + result2);
System.out.println("Combined result: " + combinedFuture.get());
CompletableFuture<Integer> composedFuture = future1.thenCompose(result -> CompletableFuture.supplyAsync(() -> result * 10));
System.out.println("Composed result: " + composedFuture.get());
}
}
在上面的範例中,我們展示了thenCombine
和thenCompose
的用法。thenCombine
用於組合兩個獨立的結果,而thenCompose
用於串聯兩個操作的結果。
StampedLock
的樂觀讀和悲觀讀有什麼區別?回答: StampedLock
是java.util.concurrent
包中的一種鎖機制,支援三種存取模式:樂觀讀、悲觀讀和寫。
樂觀讀(Optimistic Read): 樂觀讀是一種無鎖的讀操作,執行緒不會阻塞等待鎖的釋放。執行緒通過tryOptimisticRead()
方法嘗試獲取樂觀讀鎖,然後進行讀操作。在讀操作完成後,執行緒需要呼叫validate()
方法來檢查鎖是否仍然有效。如果在讀操作期間沒有其他執行緒進行寫操作,則讀操作是有效的,否則需要轉為悲觀讀。
悲觀讀(Read): 悲觀讀是一種傳統的讀操作,執行緒會獲取悲觀讀鎖,其他執行緒無法獲取寫鎖。悲觀讀鎖在寫鎖釋放之前會一直保持,可能會導致寫鎖等待。
寫(Write): 寫操作是獨佔性的,執行緒獲取寫鎖後,其他執行緒無法獲取讀鎖或寫鎖。
StampedLock
的樂觀讀適用於讀多寫少的場景,可以提高效能。但需要注意,樂觀讀在檢查鎖的有效性時可能會失敗,需要重新嘗試或轉為悲觀讀。
ThreadPoolExecutor
中的核心執行緒數和最大執行緒數的區別是什麼?回答: ThreadPoolExecutor
是java.util.concurrent
包中的一個執行緒池實現,它有兩個引數與執行緒數量相關:核心執行緒數和最大執行緒數。
核心執行緒數(Core Pool Size): 核心執行緒數是執行緒池中保持的常駐執行緒數量。當有新的任務提交時,如果當前執行緒數小於核心執行緒數,會建立新的執行緒來處理任務。即使執行緒池中沒有任務,核心執行緒也不會被回收。
最大執行緒數(Maximum Pool Size): 最大執行緒數是執行緒池中允許的最大執行緒數量。當有新的任務提交時,如果當前執行緒數小於核心執行緒數,會建立新的執行緒來處理任務。但如果當前執行緒數大於等於核心執行緒數,且工作佇列已滿,執行緒池會建立新的執行緒,直到執行緒數達到最大執行緒數。
核心執行緒數和最大執行緒數的區別在於執行緒的回收。核心執行緒數的執行緒不會被回收,最大執行緒數的執行緒在空閒一段時間後會被回收。這可以根據任務負載的情況來靈活調整執行緒池中的執行緒數量。
ThreadPoolExecutor
中的拒絕策略有哪些?如何選擇合適的拒絕策略?回答: ThreadPoolExecutor
中的拒絕策略用於處理當任務提交超過執行緒池容量時的情況,即執行緒池已滿。以下是常見的拒絕策略:
AbortPolicy
: 預設的拒絕策略,當執行緒池已滿時,新的任務提交會丟擲RejectedExecutionException
異常。
CallerRunsPolicy
: 當執行緒池已滿時,新的任務會由提交任務的執行緒來執行。這樣可以避免任務被拋棄,但可能會影響提交任務的執行緒的效能。
DiscardPolicy
: 當執行緒池已滿時,新的任務會被直接丟棄,不會丟擲異常,也不會執行。
DiscardOldestPolicy
: 當執行緒池已滿時,新的任務會丟棄等待佇列中最舊的任務,然後嘗試將新任務新增到佇列。
選擇合適的拒絕策略取決於業務需求和應用場景。如果對任務丟失比較敏感,可以選擇CallerRunsPolicy
,保證任務不會被丟棄。如果不關心丟失一些任務,可以選擇`Discard
Policy或
DiscardOldestPolicy。如果希望瞭解任務被拒絕的情況,可以選擇
AbortPolicy並捕獲
RejectedExecutionException`。
ForkJoinTask
的fork()
和join()
方法有什麼作用?回答: ForkJoinTask
是java.util.concurrent
包中用於支援分治任務的基礎類別,它有兩個重要的方法:fork()
和join()
。
fork()
方法: fork()
方法用於將當前任務進行拆分,生成子任務並將子任務提交到ForkJoinPool
中執行。子任務的執行可能會遞迴地進行拆分,形成任務樹。
join()
方法: join()
方法用於等待子任務的執行結果。在呼叫join()
方法時,當前執行緒會等待子任務的執行完成,然後獲取子任務的結果。如果子任務還有未完成的子任務,join()
方法也會遞迴等待。
fork()
和join()
方法的使用可以實現分治任務的並行處理,將大任務拆分為小任務,然後將子任務的結果合併。這有助於提高任務的並行性和效率。
ThreadLocal
是什麼?它的作用是什麼?回答: ThreadLocal
是java.lang
包中的一個類,用於在多執行緒環境中為每個執行緒提供獨立的變數副本。每個執行緒可以獨立地存取自己的變數副本,互不干擾。ThreadLocal
通常被用來解決執行緒安全問題和避免執行緒間共用變數造成的競爭問題。
ThreadLocal
的作用主要有兩個方面:
執行緒隔離: 每個執行緒可以獨立地使用自己的ThreadLocal
變數,而不會受到其他執行緒的影響。這可以避免執行緒安全問題,允許每個執行緒在多執行緒環境中擁有自己的狀態。
上下文傳遞: ThreadLocal
可以用於在同一執行緒的不同方法之間傳遞上下文資訊,而不需要顯式地傳遞引數。這對於一些跨方法、跨類的呼叫場景非常有用。
範例:
public class ThreadLocalExample {
private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
public static void main(String[] args) {
Runnable task = () -> {
int value = threadLocal.get();
System.out.println(Thread.currentThread().getName() + " initial value: " + value);
threadLocal.set(value + 1);
value = threadLocal.get();
System.out.println(Thread.currentThread().getName() + " updated value: " + value);
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
}
}
在上面的範例中,我們展示了ThreadLocal
的用法。每個執行緒可以獨立地使用自己的ThreadLocal
變數,並且在不同執行緒之間互不干擾。
ThreadLocal
的記憶體漏失問題如何避免?回答: 儘管ThreadLocal
提供了執行緒隔離的能力,但在某些情況下會導致記憶體漏失。當ThreadLocal
變數被建立後,如果沒有手動清理,它會一直保留對執行緒的參照,導致執行緒無法被回收,從而可能引發記憶體漏失問題。
為了避免ThreadLocal
的記憶體漏失,可以考慮以下幾點:
及時清理: 在使用完ThreadLocal
變數後,應該呼叫remove()
方法將變數從當前執行緒中移除,以便執行緒可以被回收。可以使用try-finally
塊來確保在任何情況下都會清理。
使用WeakReference
: 可以使用WeakReference
來
持有ThreadLocal
變數,使得變數不會阻止執行緒的回收。但需要注意,這可能會導致變數在不需要的時候被提前回收。
InheritableThreadLocal
: InheritableThreadLocal
允許子執行緒繼承父執行緒的ThreadLocal
變數,但仍然需要注意及時清理,以避免子執行緒的變數參照造成洩漏。範例:
public class ThreadLocalMemoryLeakExample {
private static ThreadLocal<Object> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
threadLocal.set(new Object());
// ... Some operations
// Ensure to remove the thread-local variable
threadLocal.remove();
}
}
在上面的範例中,我們在使用完ThreadLocal
變數後呼叫了remove()
方法來清理變數,避免了記憶體漏失問題。
回答: 實現執行緒安全的單例模式需要考慮多執行緒環境下的並行存取問題。以下是幾種常見的執行緒安全的單例模式實現方式:
懶漢模式(Double-Check Locking): 在第一次使用時才建立範例,使用雙重檢查來確保只有一個執行緒建立範例。需要使用volatile
修飾範例變數,以保證在多執行緒環境下的可見性。
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
靜態內部類模式: 使用靜態內部類來持有範例,實現懶載入和執行緒安全。由於靜態內部類只會在被參照時載入,因此實現了懶載入的效果。
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
列舉單例模式: 列舉型別天然地支援單例模式,而且在多執行緒環境下也是執行緒安全的。列舉型別的範例是在類載入時建立的。
public enum Singleton {
INSTANCE;
// Add methods and fields here
}
以上這些方式都可以實現執行緒安全的單例模式,選擇哪種方式取決於專案的需求和使用場景。
Thread.sleep()
和Object.wait()
有什麼區別?回答: Thread.sleep()
和Object.wait()
都可以用於執行緒的等待,但它們之間有一些區別:
方法來源: Thread.sleep()
是Thread
類的靜態方法,用於讓當前執行緒休眠一段時間。Object.wait()
是Object
類的實體方法,用於將當前執行緒放入物件的等待佇列中。
呼叫方式: Thread.sleep()
可以直接呼叫,無需獲取物件的鎖。Object.wait()
必須在同步塊或同步方法中呼叫,需要獲取物件的鎖。
等待目標: Thread.sleep()
只是讓執行緒休眠,不釋放任何鎖。Object.wait()
會釋放呼叫物件的鎖,進入等待狀態,直到其他執行緒呼叫相同物件的notify()
或notifyAll()
方法。
使用場景: Thread.sleep()
主要用於執行緒暫停一段時間,模擬時間等待。Object.wait()
主要用於執行緒間的通訊和同步,將執行緒置於等待狀態,直到特定條件滿足。
範例:
public class WaitSleepExample {
public static void main(String[] args) {
Object lock = new Object();
// Thread.sleep() example
new Thread(() -> {
try {
Thread.sleep(1000);
System.out.println("Thread A woke up");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
// Object.wait() example
new Thread(() -> {
synchronized (lock) {
try {
lock.wait(1000);
System.out.println("Thread B woke up");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
在上面的範例中,我們展示了Thread.sleep()
和Object.wait()
的用法。Thread.sleep()
是在不同執行緒中使用的,而Object.wait()
是在同一個物件的鎖範圍內使用的。
volatile
關鍵字的作用是什麼?它解決了什麼問題?回答: volatile
是一個關鍵字,用於修飾變數,它的主要作用是確保執行緒之間對該變數的可見性和禁止指令重排序。volatile
關鍵字解決了多執行緒環境下的兩個問題:
可見性問題: 在多執行緒環境下,一個執行緒修改了一個共用變數的值,其他執行緒可能無法立即看到這個變化,從而導致錯誤的結果。volatile
關鍵字可以確保變數的修改對所有執行緒可見,即使在不同執行緒中使用不同的快取。
指令重排序問題: 編譯器和處理器為了提高效能可能會對指令進行重排序,這在單執行緒環境下不會產生問題,但在多執行緒環境下可能導致意想不到的結果。volatile
關鍵字可以防止指令重排序,確保指令按照預期順序執行。
範例:
public class VolatileExample {
private volatile boolean flag = false;
public void toggleFlag() {
flag = !flag;
}
public boolean isFlag() {
return flag;
}
public static void main(String[] args) {
VolatileExample example = new VolatileExample();
Thread writerThread = new Thread(() -> {
example.toggleFlag();
System.out.println("Flag set to true");
});
Thread readerThread = new Thread(() -> {
while (!example.isFlag()) {
// Busy-wait
}
System.out.println("Flag is true");
});
writerThread.start();
readerThread.start();
}
}
在上面的範例中,我們使用了volatile
關鍵字來確保flag
變數的可見性,使得在readerThread
中可以正確讀取到writerThread
修改的值。
回答: 執行緒安全是指在多執行緒環境下,程式或系統能夠正確地處理並行存取共用資源
而不產生資料不一致、死鎖、競態條件等問題。實現執行緒安全的目標是保障多執行緒環境下的資料一致性和正確性。
實現執行緒安全的方式有多種:
互斥鎖(Mutex): 使用鎖機制(如synchronized
關鍵字或ReentrantLock
類)來保證在同一時間只有一個執行緒能夠存取臨界區(共用資源),其他執行緒需要等待鎖的釋放。
並行集合類: 使用java.util.concurrent
包中的並行集合類,如ConcurrentHashMap
、ConcurrentLinkedQueue
等,這些集合類在多執行緒環境下提供了安全的操作。
不可變物件: 使用不可變物件來避免多執行緒環境下的資料修改問題。由於不可變物件無法被修改,多個執行緒可以同時存取而不需要額外的同步措施。
原子操作: 使用原子操作類,如AtomicInteger
、AtomicReference
等,來執行一系列操作,保證操作的原子性。
執行緒本地儲存: 使用ThreadLocal
來為每個執行緒提供獨立的變數副本,避免共用變數造成的競態條件。
函數語言程式設計: 使用函數語言程式設計正規化,避免共用狀態,通過不可變資料和純函數來實現執行緒安全。
以上這些方法可以根據具體的應用場景選擇合適的方式來實現執行緒安全。
回答: 執行緒池是一種管理和複用執行緒的機制,它在程式中預先建立一組執行緒,並將任務分配給這些執行緒來執行。執行緒池的主要目的是提高執行緒的使用效率,減少執行緒的建立和銷燬的開銷,並可以控制同時執行的執行緒數量。
使用執行緒池的好處包括:
資源管理: 執行緒池可以在需要時建立執行緒,以及線上程閒置時回收執行緒,有效管理系統的資源。
效能提升: 執行緒池可以減少執行緒的建立和銷燬開銷,避免了頻繁的執行緒建立和銷燬,提高了系統效能。
任務佇列: 執行緒池使用任務佇列來儲存待執行的任務,避免了任務的阻塞和等待,使任務得以及時執行。
執行緒複用: 執行緒池可以複用執行緒,避免了頻繁地建立新執行緒,減少了系統開銷。
執行緒控制: 執行緒池可以控制並行執行緒的數量,避免過多的執行緒導致系統資源耗盡。
Java中可以使用java.util.concurrent
包中的Executor
和ExecutorService
來建立和管理執行緒池。常用的執行緒池實現類包括ThreadPoolExecutor
和ScheduledThreadPoolExecutor
。
synchronized
關鍵字和ReentrantLock
有什麼區別?回答: synchronized
關鍵字和ReentrantLock
都可以用於實現執行緒同步,但它們之間有一些區別:
使用方式: synchronized
是Java語言內建的關鍵字,可以在方法或程式碼塊上直接使用。ReentrantLock
是java.util.concurrent.locks
包中的類,需要顯式地建立鎖物件並呼叫相關方法。
功能靈活性: ReentrantLock
提供了更多的功能,如可重入鎖、條件等待、中斷響應等,更加靈活。synchronized
只能實現基本的鎖定和解鎖。
可重入性: synchronized
關鍵字支援可重入性,同一個執行緒可以多次獲取同一個鎖。ReentrantLock
也支援可重入性,並且提供了更多的可重入性選項。
公平性: ReentrantLock
可以選擇是否按照公平策略獲取鎖。synchronized
關鍵字預設不提供公平性。
效能: 在低並行的情況下,synchronized
的效能可能更好,因為它是Java虛擬機器器內建的關鍵字。但在高並行的情況下,ReentrantLock
可能會提供更好的效能,因為它提供了更細粒度的控制。
總的來說,如果只需要簡單的鎖定和解鎖,可以使用synchronized
關鍵字。如果需要更多的靈活性和功能,可以選擇使用ReentrantLock
。
volatile
關鍵字和synchronized
關鍵字有什麼區別?回答: volatile
關鍵字和synchronized
關鍵字都用於多執行緒環境下實現執行緒安全,但它們之間有一些區別:
用途: volatile
主要用於確保變數的可見性和禁止指令重排序。synchronized
主要用於實現臨界區的互斥存取,保證多個執行緒不會同時執行一段同步程式碼。
適用範圍: volatile
關鍵字適用於變數的單一讀取和寫入操作。synchronized
關鍵字適用於一系列操作的原子性保證,可以用於方法或程式碼塊。
效能: volatile
關鍵字的效能較好,因為它不需要像synchronized
一樣
獲取和釋放鎖。synchronized
關鍵字在多執行緒競爭激烈時,可能會導致效能下降。
可重入性: volatile
關鍵字不支援可重入性,即同一個執行緒不能重複獲取同一個volatile
變數的鎖。synchronized
關鍵字支援可重入性,同一個執行緒可以多次獲取同一個鎖。
記憶體語意: volatile
關鍵字保證變數的可見性,但不保證原子性。synchronized
關鍵字既保證可見性,又保證原子性。
總的來說,volatile
關鍵字適用於簡單的變數存取,而synchronized
關鍵字適用於更復雜的同步需求。選擇哪種方式取決於問題的具體情況。
CountDownLatch
和CyclicBarrier
有什麼區別?回答: CountDownLatch
和CyclicBarrier
都是java.util.concurrent
包中用於多執行緒協調的類,但它們之間有一些區別:
用途: CountDownLatch
用於等待多個執行緒完成某項任務,當計數器減至零時,等待執行緒會被喚醒。CyclicBarrier
用於等待多個執行緒達到一個同步點,當所有執行緒都到達時,執行指定的動作。
計數方式: CountDownLatch
使用遞減計數方式,初始計數值為執行緒數,每個執行緒完成任務後會遞減計數。CyclicBarrier
使用遞增計數方式,執行緒到達同步點後計數遞增。
重用性: CountDownLatch
的計數值減至零後不會重置,因此不能重複使用。CyclicBarrier
的計數值減至零後會重置為初始值,可以重複使用。
執行緒等待: CountDownLatch
中的執行緒等待是單向的,等待執行緒只能等待計數減至零,無法重複等待。CyclicBarrier
中的執行緒等待是迴圈的,執行緒到達同步點後會等待其他執行緒到達,然後繼續執行。
範例:
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
public class CoordinationExample {
public static void main(String[] args) throws InterruptedException {
int threadCount = 3;
CountDownLatch latch = new CountDownLatch(threadCount);
CyclicBarrier barrier = new CyclicBarrier(threadCount);
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
System.out.println("Thread " + Thread.currentThread().getId() + " is working");
try {
Thread.sleep(1000);
latch.countDown(); // CountDownLatch usage
barrier.await(); // CyclicBarrier usage
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("Thread " + Thread.currentThread().getId() + " finished");
}).start();
}
latch.await(); // Wait for all threads to complete
System.out.println("All threads completed");
// Reuse CyclicBarrier
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
System.out.println("Thread " + Thread.currentThread().getId() + " is waiting at barrier");
try {
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("Thread " + Thread.currentThread().getId() + " resumed");
}).start();
}
}
}
在上面的範例中,我們展示了CountDownLatch
和CyclicBarrier
的用法。CountDownLatch
用於等待所有執行緒完成,CyclicBarrier
用於等待所有執行緒達到同步點。
回答: 執行緒死鎖是指兩個或多個執行緒在爭奪資源時,由於資源互斥而相互等待,導致程式無法繼續執行的狀態。通常,執行緒死鎖需要滿足以下四個條件:
互斥條件: 資源不能被多個執行緒共用,只能由一個執行緒佔用。
佔有和等待條件: 一個執行緒已經佔用了資源,同時還在等待其他執行緒佔有的資源。
不可搶佔條件: 已經佔用資源的執行緒不能被其他執行緒強制搶佔,資源只能在被釋放後才能被其他執行緒獲取。
迴圈等待條件: 一組執行緒形成了一個迴圈等待的等待鏈,每個執行緒都在等待下一個執行緒所持有的資源。
要避免執行緒死鎖,可以採取以下幾種策略:
破壞佔有和等待條件: 一次性獲取所有需要的資源,或者在獲取資源時不等待,而是立即釋放已佔有的資源。
破壞不可搶佔條件: 允許執行緒釋放已佔有的資源,並等待其他執行緒釋放資源後重新獲取。
破壞迴圈等待條件: 引入資源的順序分配,使得執行緒按照一定的順序獲取資源,從而避免形成迴圈等待。
使用超時等待: 在獲取資源時設定超時時間,如果在一定時間內無法獲取資源,則放棄當前操作。
使用死鎖檢測和解除機制: 藉助工具或演演算法檢測死鎖並進行解鎖,如銀行家演演算法等。
回答: 執行緒飢餓是指某個或某些執行緒無法獲得所需的資源或執行機會,導致長時間處於等待狀態,無法正常執行的情況。執行緒飢餓可能導致程式的效能下降和響應延遲。
要避免執行緒飢餓,可以採取以下幾種方法:
公平排程: 使用公平的排程演演算法,確保每個執行緒都有機會獲得資源和執行時間,避免某個執行緒一直處於等待狀態。
優先順序設定: 為執行緒設定適當的優先順序,高優先順序的執行緒更有機會獲得資源和執行時間,但要避免設定過高的優先順序導致其他執行緒無法執行。
使用鎖的公平性: 在使用鎖時,可以選擇使用公平鎖,以確保等待時間最長的執行緒優先獲得鎖。
執行緒池設定: 合理設定執行緒池的引數,確保每個執行緒都有機會被執行,避免某些執行緒一直處於等待狀態。
避免大量計算: 在某些情況下,執行緒飢餓可能是因為某個執行緒執行了大量的計算操作,導致其他執行緒無法獲得執行機會。可以將大量計算放入後臺執行緒,避免影響其他執行緒的執行。
回答: 執行緒間的通訊是指多個執行緒在執行過程中通過某種機制進行資訊交換、資料共用或協調操作的過程。執行緒間通訊主要是為了實現資料同步、任務共同作業等目的。
常見的執行緒間通訊機制包括:
共用變數: 多個執行緒共用同一個變數,通過對變數的讀寫來進行資訊交換。使用volatile
關鍵字或synchronized
關鍵字來保證共用變數的可見性和執行緒安全性。
管道和流: 使用輸入流和輸出流進行執行緒間通訊,通過流將資料從一個執行緒傳遞給另一個執行緒。
wait()和notify(): 使用Object
類的wait()
和notify()
方法來實現等待和通知機制,允許執行緒在特定條件下等待和被喚醒。
阻塞佇列: 使用BlockingQueue
實現執行緒間的生產者-消費者模式,其中一個執行緒負責生產資料,另一個執行緒負責消費資料。
號誌(Semaphore): 使用號誌來控制多個執行緒的並行存取數量,限制同時執行的執行緒數。
倒計時門閂(CountDownLatch): 使用CountDownLatch
來等待多個執行緒的任務完成,當計數減至零時,等待執行緒被喚醒。
迴圈屏障(CyclicBarrier): 使用CyclicBarrier
來等待多個執行緒到達同一個同步點,然後繼續執行。
執行緒間的通知(Thread Communication): 自定義通訊方式,通過共用變數和鎖等方式進行執行緒間的通訊,例如生產者-消費者
模式。
根據具體的應用場景,選擇合適的執行緒間通訊機制來實現資料共用和共同作業。
回答: 執行緒優先順序是用於指示執行緒排程器在有多個執行緒可執行時應該選擇哪個執行緒來執行的值。執行緒優先順序的作用是影響執行緒的排程順序,高優先順序的執行緒可能會在低優先順序執行緒之前得到執行。
在Java中,執行緒的優先順序由整數值表示,範圍從Thread.MIN_PRIORITY
(最低優先順序,值為1)到Thread.MAX_PRIORITY
(最高優先順序,值為10),預設為Thread.NORM_PRIORITY
(正常優先順序,值為5)。
要設定執行緒的優先順序,可以使用setPriority(int priority)
方法,例如:
Thread thread = new Thread(() -> {
// Thread code
});
thread.setPriority(Thread.MAX_PRIORITY); // Set thread priority
thread.start();
需要注意的是,執行緒的優先順序只是給執行緒排程器一個提示,但並不能保證執行緒優先順序一定會被嚴格遵循,因為執行緒排程依賴於作業系統和Java虛擬機器器的具體實現。
回答: 執行緒組是一種用於管理多個執行緒的機制,可以將多個執行緒組織成一個樹狀結構。執行緒組的作用是對執行緒進行分組管理,可以方便地對一組執行緒進行控制和操作。
執行緒組的主要作用包括:
方便管理: 執行緒組允許將多個相關的執行緒組織到一起,方便管理和監控。
批次操作: 可以對整個執行緒組進行批次操作,如暫停、恢復、中斷等。
例外處理: 可以設定執行緒組的未捕獲例外處理器,用於處理執行緒組中任何執行緒丟擲的未捕獲異常。
優先順序設定: 可以設定執行緒組的優先順序,影響組中所有執行緒的優先順序。
活躍執行緒統計: 可以通過執行緒組統計活躍執行緒數等資訊。
在Java中,執行緒組是通過ThreadGroup
類來表示的。建立執行緒組後,可以將執行緒新增到執行緒組中,也可以建立子執行緒組。執行緒組可以通過建構函式指定父執行緒組,從而形成一個樹狀的執行緒組結構。
回答: 守護執行緒是在程式執行時在後臺提供一種通用服務的執行緒,它不會阻止程式的終止,即使所有非守護執行緒都已經結束,守護執行緒也會隨著程式的終止而自動退出。相反,非守護執行緒(使用者執行緒)會阻止程式的終止,直到所有非守護執行緒都已經結束。
在Java中,可以使用setDaemon(true)
方法將執行緒設定為守護執行緒。如果沒有顯式設定,執行緒預設是非守護執行緒。
範例程式碼如下:
Thread daemonThread = new Thread(() -> {
while (true) {
// Background task
}
});
daemonThread.setDaemon(true); // Set as daemon thread
daemonThread.start();
需要注意的是,守護執行緒在執行時可能會被強制中斷,因此在設計守護執行緒時需要確保執行緒執行不會對程式的穩定性造成影響。
回答: 執行緒的生命週期是指一個執行緒從建立到終止的整個過程,包括多個狀態和狀態之間的轉換。Java中的執行緒生命週期包括以下幾個狀態:
新建狀態(New): 執行緒被建立但還未啟動。
就緒狀態(Runnable): 執行緒已經建立並啟動,但尚未分配到CPU執行。
執行狀態(Running): 執行緒已經分配到CPU執行。
阻塞狀態(Blocked): 執行緒因為等待某個條件的滿足而暫時停止執行,例如等待I/O操作完成。
等待狀態(Waiting): 執行緒因為等待某個條件的滿足而暫時停止執行,需要其他執行緒顯式地喚醒。
計時等待狀態(Timed Waiting): 執行緒因為等待某個條件的滿足而暫時停止執行,但會在一定時間後自動恢復。
終止狀態(Terminated): 執行緒執行完成或出現異常而終止。
執行緒的狀態可以通過Thread.getState()
方法來獲取。執行緒會根據程式的執行情況在不同的狀態之間切換,如就緒狀態、執行狀態、阻塞狀態等。
ThreadLocal
實現執行緒間的資料隔離?回答: ThreadLocal
是一種執行緒區域性變數,它可以在每個執行緒中儲存不同的值,實現執行緒間的資料隔離。每個執行緒都可以存取和修改自己執行緒內部的ThreadLocal
變數,不同執行緒之間的變數互不干擾。
要使用`Thread
Local`實現執行緒間的資料隔離,可以按照以下步驟:
建立ThreadLocal
物件:使用ThreadLocal
的子類(如ThreadLocal<Integer>
)建立一個ThreadLocal
物件。
設定和獲取值:通過set(T value)
方法設定執行緒的區域性變數值,使用get()
方法獲取執行緒的區域性變數值。
範例程式碼如下:
public class ThreadLocalExample {
private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
public static void main(String[] args) {
Runnable task = () -> {
int value = threadLocal.get();
System.out.println("Thread " + Thread.currentThread().getId() + " initial value: " + value);
threadLocal.set(value + 1);
value = threadLocal.get();
System.out.println("Thread " + Thread.currentThread().getId() + " updated value: " + value);
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
}
}
在上面的範例中,ThreadLocal
物件threadLocal
被兩個執行緒共用,但每個執行緒都擁有自己的區域性變數副本。這樣就實現了執行緒間的資料隔離,每個執行緒的變數互不影響。
回答: 執行緒安全問題是指在多執行緒環境下,多個執行緒對共用資源進行讀寫操作時可能出現的資料不一致、競態條件、死鎖等問題。解決執行緒安全問題的方法包括:
互斥鎖(Mutex): 使用鎖機制來保證在同一時間只有一個執行緒能夠存取共用資源,例如使用synchronized
關鍵字或ReentrantLock
類。
並行集合類: 使用java.util.concurrent
包中的並行集合類,如ConcurrentHashMap
、ConcurrentLinkedQueue
,來保證多執行緒下的安全存取。
不可變物件: 使用不可變物件來避免多執行緒環境下的資料修改問題,因為不可變物件無法被修改,多執行緒可以共用存取。
原子操作: 使用原子操作類,如AtomicInteger
、AtomicReference
等,來執行一系列操作,保證操作的原子性。
執行緒本地儲存: 使用ThreadLocal
為每個執行緒提供獨立的變數副本,避免共用變數造成的競態條件。
函數語言程式設計: 使用函數語言程式設計正規化,避免共用狀態,通過不可變資料和純函數來實現執行緒安全。
volatile
關鍵字的作用是什麼?它解決了哪些問題?回答: volatile
關鍵字用於宣告一個變數是「易變的」(volatile),告訴編譯器和執行時環境,這個變數可能被多個執行緒同時存取,從而禁止對該變數的一些優化,保證了變數的可見性和有序性。它主要解決了以下兩個問題:
可見性問題: 在多執行緒環境中,一個執行緒對變數的修改可能對其他執行緒不可見,導致讀取到的值不一致。使用volatile
關鍵字可以保證變數的修改對其他執行緒是可見的。
指令重排序問題: 編譯器和處理器為了提高效能可能會對指令進行重排序,可能導致某些指令在多執行緒環境下執行順序不一致。使用volatile
關鍵字可以禁止對volatile
變數的部分優化,保證指令不會被過度重排。
範例程式碼如下:
public class VolatileExample {
private volatile boolean flag = false;
public void toggleFlag() {
flag = !flag;
}
public boolean isFlag() {
return flag;
}
public static void main(String[] args) {
VolatileExample example = new VolatileExample();
Thread writerThread = new Thread(() -> {
example.toggleFlag();
System.out.println("Flag is set to true");
});
Thread readerThread = new Thread(() -> {
while (!example.isFlag()) {
// Wait for the flag to become true
}
System.out.println("Flag is true");
});
writerThread.start();
readerThread.start();
}
}
在上面的範例中,volatile
關鍵字確保了flag
變數的修改對讀取執行緒是可見的,避免了讀取執行緒一直處於等待狀態。
volatile
關鍵字和synchronized
關鍵字有什麼區別?回答: volatile
關鍵字和synchronized
關鍵字都用於實現執行緒安全,但它們之間有以下幾個區別:
作用範圍: volatile
關鍵字主要用於確保變數的可見性,適用於單一變數的讀取和寫入。synchronized
關鍵字用於實現一段同步程式碼的互斥存取。
功能: volatile
關鍵字主要解決變數的可見性問題,禁止對指令重排序。synchronized
關鍵字既保證可見性,又保證了原子性和互斥性。
適用場景: volatile
適用於簡單的變數讀寫場景,不適合複雜的操作。synchronized
適用於臨界區的互斥存取,可以用於方法或程式碼塊。
效能: volatile
關鍵字的效能較好,不會像synchronized
那樣涉及鎖的獲取和釋放。synchronized
關鍵字的效能較差,涉及鎖的獲取和釋放。
可重入性: volatile
關鍵字不支援可重入性,即同一個執行緒不能重複獲取同一個volatile
變數的鎖。synchronized
關鍵字支援可重入性,同一個執行緒可以多次獲取同一個鎖。
適用物件: volatile
關鍵字只能修飾變數。synchronized
關鍵字可以修飾方法、程式碼塊,以及修飾靜態方法和非靜態方法。
總之,volatile
關鍵字主要用於實現變數的可見性,而synchronized
關鍵字用於實現互斥和原子性操作。選擇哪種關鍵字取決於具體問題的需求。
回答: 執行緒池是一種用於管理和複用執行緒的機制,它在程式啟動時建立一組執行緒,並將任務分配給這些執行緒執行。執行緒池的主要目的是降低建立和銷燬執行緒的開銷,提高執行緒的複用性和執行效率。
使用執行緒池的好處包括:
降低執行緒建立銷燬開銷: 建立和銷燬執行緒是一種開銷較大的操作,執行緒池可以減少這些開銷,通過複用執行緒來執行多個任務。
提高系統資源利用率: 執行緒池可以控制執行緒的數量,避免過多執行緒導致系統資源耗盡。
提高響應速度: 使用執行緒池可以減少執行緒的建立時間,從而提高任務的響應速度。
簡化執行緒管理: 執行緒池可以自動管理執行緒的建立、銷燬和排程,簡化了執行緒管理的複雜性。
控制並行度: 可以通過執行緒池的引數來控制並行執行的執行緒數量,防止系統過載。
在Java中,可以使用java.util.concurrent.Executors
類來建立執行緒池,常見的執行緒池型別有FixedThreadPool
、CachedThreadPool
、ScheduledThreadPool
等。
回答: 執行緒死鎖是指多個執行緒因為相互等待對方釋放資源而陷入無法繼續執行的狀態。執行緒死鎖通常由於多個執行緒同時持有一些共用資源的鎖,然後試圖獲取其他執行緒持有的鎖而導致的。
要診斷執行緒死鎖,可以使用工具進行監控和分析,如使用JConsole、VisualVM等。
為了避免執行緒死鎖,可以採取以下策略:
按順序獲取鎖: 執行緒按照統一的順序獲取鎖,避免不同執行緒持有不同的鎖的順序導致死鎖。
使用超時等待: 執行緒在嘗試獲取鎖時設定超時時間,如果在一定時間內無法獲取到鎖,放棄操作並釋放已經持有的鎖。
使用tryLock()
: 使用tryLock()
方法嘗試獲取鎖,如果無法獲取到鎖立即釋放已經持有的鎖。
使用Lock
介面: 使用java.util.concurrent.locks.Lock
介面的tryLock()
方法,允許指定獲取鎖的等待時間。
避免巢狀鎖: 儘量避免在持有一個鎖的時候再去獲取其他鎖,儘量減少鎖的巢狀層次。
使用死鎖檢測: 使用死鎖檢測機制,可以檢測並解除發生的死鎖。
回答: 執行緒飢餓是指某些執行緒無法獲得所需的資源或執行機會,導致長時間處於等待狀態,無法正常執行的情況。執行緒飢餓可能導致程式的效能下降和響應延遲。
為了避免執行緒飢餓,可以採取以下方法:
公平排程: 使用公平的排程演演算法,確保每個執行緒都有機會獲得資源和執行時間,避免某個執行緒一直處於等待狀態。
優先順序設定: 為執行緒設定適當的優先順序,高優先順序的執行緒更有機會獲得資源和執行時間,但要避免設定過高的優先順序導致其他執行緒無法執行。
使用鎖的公平性: 在使用鎖時,可以選擇使用公平鎖,以確保等待時間最長的執行緒優先獲得鎖。
執行緒池設定: 合理設定執行緒池的引數,確保每個執行緒都有機會被執行,避免某些執行緒一直處於等待狀態。
避免大量計算: 在某些情況下,執行緒飢餓可能是因為某個執行緒執行了大量的計算操作,導致其他執行緒無法獲得執行機會。可以將大量計算放入後臺執行緒,避免影響其他執行緒的執行。
回答: 執行緒間通訊是指多個執行緒在執行過程中通過某種機制進行資訊交換、資料共用或協調操作的過程。執行緒間通訊主要是為了實現資料同步、任務共同作業等目的。
常見的執行緒間通訊機制包括:
共用變數: 多個執行緒共用同一個變數,通過對變數的讀寫來進行資訊交換。使用volatile
關鍵字或synchronized
關鍵字來保證共用變數的可見性和執行緒安全性。
管道和流: 使用輸入流和輸出流進行執行緒間通
信,通過流將資料從一個執行緒傳遞給另一個執行緒。
wait()和notify(): 使用Object
類的wait()
和notify()
方法來實現等待和通知機制,允許執行緒在特定條件下等待和被喚醒。
阻塞佇列: 使用BlockingQueue
實現執行緒間的生產者-消費者模式,其中一個執行緒負責生產資料,另一個執行緒負責消費資料。
號誌(Semaphore): 使用號誌來控制多個執行緒的並行存取數量,限制同時執行的執行緒數。
倒計時門閂(CountDownLatch): 使用CountDownLatch
來等待多個執行緒的任務完成,當計數減至零時,等待執行緒被喚醒。
迴圈屏障(CyclicBarrier): 使用CyclicBarrier
來等待多個執行緒到達同一個同步點,然後繼續執行。
執行緒間的通知(Thread Communication): 自定義通訊方式,通過共用變數和鎖等方式進行執行緒間的通訊,例如生產者-消費者模式。
根據具體的應用場景,選擇合適的執行緒間通訊機制來實現資料共用和共同作業。
回答: 執行緒優先順序是用於指示執行緒排程器在有多個執行緒可執行時應該選擇哪個執行緒來執行的值。執行緒優先順序的作用是影響執行緒的排程順序,高優先順序的執行緒可能會在低優先順序執行緒之前得到執行。
在Java中,執行緒的優先順序由整數值表示,範圍從Thread.MIN_PRIORITY
(最低優先順序,值為1)到Thread.MAX_PRIORITY
(最高優先順序,值為10),預設為Thread.NORM_PRIORITY
(正常優先順序,值為5)。
要設定執行緒的優先順序,可以使用setPriority(int priority)
方法,例如:
Thread thread = new Thread(() -> {
// Thread code
});
thread.setPriority(Thread.MAX_PRIORITY); // Set thread priority
thread.start();
需要注意的是,執行緒的優先順序只是給執行緒排程器一個提示,但並不能保證執行緒優先順序一定會被嚴格遵循,因為執行緒排程依賴於作業系統和Java虛擬機器器的具體實現。
回答: 執行緒組是一種用於管理多個執行緒的機制,可以將多個執行緒組織成一個樹狀結構。執行緒組的作用是對執行緒進行分組管理,可以方便地對一組執行緒進行控制和操作。
執行緒組的主要作用包括:
方便管理: 執行緒組允許將多個相關的執行緒組織到一起,方便管理和監控。
批次操作: 可以對整個執行緒組進行批次操作,如暫停、恢復、中斷等。
例外處理: 可以設定執行緒組的未捕獲例外處理器,用於處理執行緒組中任何執行緒丟擲的未捕獲異常。
優先順序設定: 可以設定執行緒組的優先順序,影響組中所有執行緒的優先順序。
活躍執行緒統計: 可以通過執行緒組統計活躍執行緒數等資訊。
在Java中,執行緒組是通過ThreadGroup
類來表示的。建立執行緒組後,可以將執行緒新增到執行緒組中,也可以建立子執行緒組。執行緒組可以通過建構函式指定父執行緒組,從而形成一個樹狀的執行緒組結構。
回答: 守護執行緒是在程式執行時在後臺提供一種通用服務的執行緒,它不會阻止程式的終止,即使所有非守護執行緒都已經結束,守護執行緒也
會隨著程式的終止而自動退出。相反,非守護執行緒(使用者執行緒)會阻止程式的終止,直到所有非守護執行緒都已經結束。
在Java中,可以使用setDaemon(true)
方法將執行緒設定為守護執行緒。如果沒有顯式設定,執行緒預設是非守護執行緒。
範例程式碼如下:
Thread daemonThread = new Thread(() -> {
while (true) {
// Background task
}
});
daemonThread.setDaemon(true); // Set as daemon thread
daemonThread.start();
需要注意的是,守護執行緒在執行時可能會被強制中斷,因此在設計守護執行緒時需要確保執行緒執行不會對程式的穩定性造成影響。
回答: 執行緒的生命週期是指一個執行緒從建立到終止的整個過程,包括多個狀態和狀態之間的轉換。Java中的執行緒生命週期包括以下幾個狀態:
新建狀態(New): 執行緒被建立但還未啟動。
就緒狀態(Runnable): 執行緒已經建立並啟動,但尚未分配到CPU執行。
執行狀態(Running): 執行緒已經分配到CPU執行。
阻塞狀態(Blocked): 執行緒因為等待某個條件的滿足而暫時停止執行,例如等待I/O操作完成。
等待狀態(Waiting): 執行緒因為等待某個條件的滿足而暫時停止執行,需要其他執行緒顯式地喚醒。
計時等待狀態(Timed Waiting): 執行緒因為等待某個條件的滿足而暫時停止執行,但會在一定時間後自動恢復。
終止狀態(Terminated): 執行緒執行完成或出現異常而終止。
執行緒的狀態可以通過Thread.getState()
方法來獲取。執行緒會根據程式的執行情況在不同的狀態之間切換,如就緒狀態、執行狀態、阻塞狀態等。
ThreadLocal
實現執行緒間的資料隔離?回答: ThreadLocal
是一種執行緒區域性變數,它可以在每個執行緒中儲存不同的值,實現執行緒間的資料隔離。每個執行緒都可以存取和修改自己執行緒內部的ThreadLocal
變數,不同執行緒之間的變數互不干擾。
要使用ThreadLocal
實現執行緒間的資料隔離,可以按照以下步驟:
建立ThreadLocal
物件:使用ThreadLocal
的子類(如ThreadLocal<Integer>
)建立一個ThreadLocal
物件。
設定和獲取值:通過set(T value)
方法設定執行緒的區域性變數值,使用get()
方法獲取執行緒的區域性變數值。
範例程式碼如下:
public class ThreadLocalExample {
private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
public static void main(String[] args) {
Runnable task = () -> {
int value = threadLocal.get();
System.out.println("Thread " + Thread.currentThread().getId() + " initial value: " + value);
threadLocal.set(value + 1);
value = threadLocal.get();
System.out.println("Thread " + Thread.currentThread().getId() + " updated value: " + value);
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
}
}
在上面的範例中,ThreadLocal
物件threadLocal
被兩個執行緒共用,但每個執行緒都擁有自己的區域性變數副本。這樣就實現了執行緒間的資料隔離,每個執行緒的變數互不影響。