1、多線程如何共享資料
多線程資料共享可以分為以下2種情況,線程實作代碼相同及線程實作代碼不同。
- 線程實作代碼相同
即runnable中的代碼一緻,這樣可以直接在實作中定義成員變量直接共享
public class SharedSameRunnableDemo {
public static void main(String[] args) {
Runnable runnable = new MySameRunnable();
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
}
private static class MySameRunnable implements Runnable {
private int sharedData = 0;
@Override
public synchronized void run() {
for (int i = 0; i < 5; i++) {
sharedData++;
System.out.println("Thread: " + Thread.currentThread().getName() + ",
sharedData: " + sharedData);
}
}
}
}
在上面的示例中,我們定義了一個名為 MySameRunnable 的内部類它的共享變量是sharedData,它實作了Runnable 接口,并重寫了 run() 方法。在 run() 方法中,我們使用了 synchronized 關鍵字來保證線程安全。然後在main() 方法中,我們建立了一個 MySameRunnable 執行個體 runnable,并将其傳入兩個不同的線程對象中。最後啟動這兩個線程。由于這兩個線程共享同一個 MySameRunnable 執行個體,是以它們執行的代碼是相同的,并且可以通路和修改sharedData 變量。通過這種方式,就可以實作多個線程共享資料,并確定線程安全。
- 線程實作代碼不相同
即runnable中的代碼不一緻,MyRunnable1 MyRunnable2,利用一個對象SharedData,把runnable中的方法封裝到這個對象中去,資料也在這個對象中。如果多個線程實作的代碼不同,并且需要共享變量,可以使用一個單獨的類來存儲這些共享變量,并将它傳遞給所有的 Runnable 執行個體。以下是一個簡單的示例代碼:
public class SharedDifferentRauuableDemo {
public static void main(String[] args) {
SharedData sharedData = new SharedData();
Thread thread1 = new Thread(new MyRunnable1(sharedData));
Thread thread2 = new Thread(new MyRunnable2(sharedData));
thread1.start();
thread2.start();
}
private static class SharedData {
private int data = 0;
public synchronized void increment() {
data++;
System.out.println("IncrementThread: " + data);
}
public synchronized void decrement() {
data--;
System.out.println("DecrementThread: " + data);
}
}
private static class MyRunnable1 implements Runnable {
private SharedData sharedData;
public MyRunnable1(SharedData sharedData) {
this.sharedData = sharedData;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
sharedData.increment();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
private static class MyRunnable2 implements Runnable {
private SharedData sharedData;
public MyRunnable2(SharedData sharedData) {
this.sharedData = sharedData;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
sharedData.decrement();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
在上面的示例中,我們定義了一個 SharedData 類來存儲共享變量 data。這個類包含兩個同步方法 increment() 和 decrement(),用于增加和減少 data 的值,并輸出目前的值。然後我們建立了兩個 MyRunnable1 和 MyRunnable2 執行個體,它們分别傳遞了相同的 SharedData 對象。在 run() 方法中,它們調用 SharedData 對象的 increment() 和 decrement() 方法來進行資料修改,并使用 Thread.sleep() 方法讓線程休眠一段時間。
通過這種方式,就可以實作多個線程共享資料,并確定線程安全。
2、子線程如何繼承父線程資料
通過 InteritableThreadLocal實作共享
public class InheritableThreadLocalDemo {
private static final InheritableThreadLocal<String> inheritableThreadLocal
= new InheritableThreadLocal<>();
public static void main(String[] args) {
inheritableThreadLocal.set("Hello, World!");
Thread parentThread = new Thread(() -> {
System.out.println("Parent Thread: " + inheritableThreadLocal.get());
inheritableThreadLocal.set("Hello from Parent Thread!");
Thread childThread = new Thread(() -> {
System.out.println("Child Thread: " + inheritableThreadLocal.get());
});
childThread.start();
try {
childThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Parent Thread: " + inheritableThreadLocal.get());
});
parentThread.start();
try {
parentThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Main Thread: " + inheritableThreadLocal.get());
inheritableThreadLocal.remove();
}
}
在上面的示例中,我們建立了一個 InheritableThreadLocal 對象,并将其設定為“Hello, World!”。然後,我們建立了一個名為“parentThread”的線程,并在其中輸出 inheritableThreadLocal 的值。接下來,我們在父線程中将 inheritableThreadLocal 的值設定為“Hello from Parent Thread!”,并建立了一個名為“childThread”的線程,在其中輸出 inheritableThreadLocal 的值。
注意:由于 InheritableThreadLocal 是可繼承的,是以在子線程中也能夠擷取到父線程中設定的值。是以,當我們在子線程中輸出 inheritableThreadLocal 的值時,我們将看到輸出“Hello from Parent Thread!”。
最後,我們分别在父線程、子線程和主線程中輸出 inheritableThreadLocal 的值。由于我們在每個線程中都設定了 inheritableThreadLocal 的值,是以每個線程都将輸出不同的值。
請注意,在程式的結尾處,我們調用了 inheritableThreadLocal.remove() 方法來清除 InheritableThreadLocal 對象的值,并釋放相關的資源。這是一種良好的習慣,可以避免記憶體洩漏和資料污染等問題。
3、相關問題
- 請簡述Java中的多線程通信機制,并解釋一下為什麼需要多線程通信?
答:Java中的多線程通信機制是通過使用管道和共享變量來實作的。管道可以用來實作多個線程之間的資料傳遞和同步,共享變量可以用來實作多個線程之間的資料共享和同步。
- Java 中,在多個線程之間共享資料的方式主要有以下幾種:
1)共享變量:可以将需要共享的變量定義為靜态變量或公共變量,然後通過同步控制機制(例如 synchronized 關鍵字、Lock 接口等)保證多線程通路這些變量時的安全性。
2)ThreadLocal:通過 ThreadLocal 類可以在每個線程中建立獨立的變量副本,進而避免了對共享變量的競争。
3)Callable 和 Future 接口:在子線程執行任務後,可以通過 Callable 和 Future 接口傳回結果給主線程。主線程可以通過 Future.get() 方法擷取子線程的執行結果,進而完成資料共享。
4)BlockingQueue:可以使用 BlockingQueue 來實作資料共享和通信,生産者線程向 BlockingQueue 中添加資料,消費者線程從隊列中擷取資料。
5)CyclicBarrier 和 CountDownLatch:可以使用 CyclicBarrier 和 CountDownLatch 等同步工具來協調多個線程之間的操作,進而實作資料共享。