多線程
線程:類似執行一個命令,多線程:併發執行多條命令。
多線程的優點:
1.充分利用cpu的性能。
2.提高系統性能。
3.同一時刻處理可以處理不同的命令
線程同步
即當有一個線程在對內存進行操作時,其他線程都不可以對這個內存地址進行操作,直到該線程完成操作,爲什麼需要它呢?
1.多線程會出現線程安全問題,線程同步可以有效的保證線程安全。
2.當主線程依賴兩個子線程結果的時候,需要線程同步
如何實現線程同步?
1.加鎖,如:synchronized。
2.通過wait和和notify(和notifyAll),推薦使用notifyAll。
3.線程池callback。
4.join()。
5.CountDownLatch(java SDK包)。
6.CyclicBarrier(java SDK包)。
等等。。。。。。。。。。。。。。。。
這裏只介紹5、6兩種
我們先來看一個沒有加入線程同步的代碼:
public static void hello(){
System.out.println("線程:"+Thread.currentThread().getName()+" 執行了。。。。。。。。。");
}
public static void main(String[] args) {
//線程1
Thread t1 = new Thread(() -> {
hello();
});
t1.start();
//線程2
Thread t2 = new Thread(() -> {
hello();
});
t2.start();
System.out.println("主函數執行完畢。。。。。。。。。");
}
打印結果:
main方法的輸出語句居然比兩個子線程先執行,爲什麼呢?因爲main是主線程,t1、t2是兩個子線程,由於線程的執行順序是無序的,所以就會導致每次的執行結果都不相同,現在我想實現當t1、t2執行完成之後在執行main方法的輸出語句,該如何實現呢?只需要給t1、t2分別加一個方法即可:
public static void hello(){
System.out.println("線程:"+Thread.currentThread().getName()+" 執行了。。。。。。。。。");
}
public static void main(String[] args) {
//線程1
Thread t1 = new Thread(() -> {
hello();
});
t1.start();
//線程2
Thread t2 = new Thread(() -> {
hello();
});
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主函數執行完畢。。。。。。。。。");
}
結果如下:
爲什麼join(),可以實現線程同步呢?join()源碼如下:
很明顯,這裏使用while做了循環等待,讓線程不往下執行,達到線程同步(等待)的效果。
然而我們平時的開發過程中基本不會這麼創建線程,一般都是使用線程池,那在使用線程池的情況下如何讓線程實現同步呢?
我們先試試自己寫一個方法讓它實現同步,代碼如下:
public static void hello(){
String name = Thread.currentThread().getName();
try {
System.out.println("線程:"+name+" 休眠開始。。。。。。。。。。。。");
Thread.sleep(1000);
System.out.println("線程:"+name+" 休眠結束。。。。。。。。。。。。");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// 計數器初始化爲2
AtomicInteger count = new AtomicInteger(2);
executor.execute(() ->{
hello();
count.decrementAndGet();
});
executor.execute(() ->{
hello();
count.decrementAndGet();
});
//等待兩個線程執行完畢
while(count.get() != 0){
}
System.out.println("我是在兩個線程執行之後才執行的內容");
}
count:用於統計線程執行的數量,線程執行-1;AtomicInteger:原子類,可以在多線程中保證共享變量的安全。
decrementAndGet:自減並返回自減以後的結果(原子操作)。
while:線程同步的重點:這裏主要是讓主線程處於循環狀態,直到count被減爲0,也就意味着兩個子線程都已執行完畢。
但是我不推薦這麼做,爲什麼呢?因爲java SDK給我們提供了現成的方法,我們爲啥還要自己去手動實現呢?下面我們就來看看 CountDownLatch是如何實現線程同步:
// 創建2個線程的線程池
private static Executor executor = Executors.newFixedThreadPool(2);
public static void hello(){
String name = Thread.currentThread().getName();
try {
System.out.println("線程:"+name+" 休眠開始。。。。。。。。。。。。");
Thread.sleep(1000);
System.out.println("線程:"+name+" 休眠結束。。。。。。。。。。。。");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//這裏需要注意一點,那就是實例化CountDownLatch的初始大小,一定要和你需要等待線程的數量相同,
//小了會導致等待的線程提前執行。
//大了會導致線程一直處於無限循環當中
CountDownLatch countDownLatch = new CountDownLatch(2);
executor.execute(() ->{
hello();
countDownLatch.countDown();
});
executor.execute(() ->{
hello();
countDownLatch.countDown();
});
//等待兩個線程執行完畢
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是在兩個線程執行之後才執行的內容");
}
爲了效果明顯,我特意在hello方法讓線程休眠1秒。
countDownLatch:實現線程同步的關鍵,實例化一個需要等待的線程數量
countDownLatch.countDown():等待線程數-1。
countDownLatch.await();讓主線程處於等待狀態,直到等待的線程被減爲0(注意:這裏必須要做異常捕獲線程中斷的異常:(InterruptedException);
上面代碼結果如下:
這裏需要注意一點:CountDownLatch的初始大小是不會被重置的,所以使用這個解決方案的時候需要手動重置CountDownLatch線程等待的初始大小。
實現原理:
其實查看源碼,他的實現方式和我之前使用的while類似,他這裏用了for的無限循環,直到等待的線程被減爲0;
那有沒有不需要重新設置線程等待的工具類呢?肯定是有的,那就是接下來要說的:CyclicBarrier
CyclicBarrier
主要通過線程回調來實現線程等待,這裏的實現方式稍微做了一下修改:
// 創建3個線程的線程池,其中一個線程用於回調處理主線程的事情
private static Executor executor = Executors.newFixedThreadPool(3);
public static void hello(){
String name = Thread.currentThread().getName();
try {
System.out.println("線程:"+name+" 休眠開始。。。。。。。。。。。。");
Thread.sleep(1000);
System.out.println("線程:"+name+" 休眠結束。。。。。。。。。。。。");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//這裏需要注意一點,那就是實例化CountDownLatch的初始大小,一定要和你需要等待線程的數量相同,
//小了會導致等待的線程提前執行。
//大了會導致線程一直處於無限循環當中
final CyclicBarrier barrier = new CyclicBarrier(2, ()->{ executor.execute(()->printAfter()); });
executor.execute(() ->{
hello();
try {
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
});
executor.execute(() ->{
hello();
try {
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
});
}
/**
* 兩個線程執行完畢之後執行此方法
*/
private static void printAfter() {
System.out.println("我是在兩個線程執行之後才執行的內容");
}
這裏需要注意一點,那就是主函數的輸出語句已經不放在mian方法中了,而是寫在了barrier的回調方法中。當等待的線程執行完畢之後CyclicBarrier的等待線程數會被重置。
CyclicBarrier與CountDownLatch區別
CountDownLatch:解決一個線程等待多個線程場景。
CyclicBarrier:解決一組線程之間的等待場景。
CyclicBarrier支持重置功能,CountDownLatch不支持,這點需要特別注意。