文章目錄
爲什麼要使用同步??
java允許多線程併發控制,當多線程同時操作一個可共享的資源變量時,將會導致數據不準確,相互之間產生衝突,因此加入同步鎖以避免在該線程沒有完成操作之前,被其他線程調用,從而保證了該變量的唯一性和準確性
使用特殊變量volitale實現線程同步
a.volatile關鍵字爲域變量的訪問提供一種免鎖機制
b.使用volatile修飾域相當於告訴虛擬機該域可能被其他現象更新
c.因此每次使用該域就要重新計算,而不是使用寄存器中的值
d.volatile不會提供任何原子操作,它也不能用來修飾final類型的變量
使用ThreadLocal局部變量實現線程
如果使用ThreadLocal管理變量,則每一個使用變量的線程都獲得該變量的副本,副本之間相互獨立,這樣每一個線程都可以隨意修改自己的變量副本,而不會對其他線程產生影響。
使用原子變量實現線程同步
需要使用線程同步的根本原因在於對普通變量的操作不是原子的。
那麼什麼是原子操作呢?
原子操作就是指將讀取變量值、修改變量值、保存變量值看成一個整體來操作
即-這幾種行爲要麼同時完成,要麼都不完成。
在java的util.concurrent.atomic包中提供了創建了原子類型變量的工具類,
使用該類可以簡化線程同步。
重入鎖
)
synchronized
ReentranLock
重入鎖可以完全替代
synchronized
關鍵字,在JDK5的早期版本中,重入鎖的性能遠遠好於synchronized
,但從JDK6開始,JDK在Synchronized上做了大量的優化,使得二者的性能差距並不大。
讀寫鎖
讀寫鎖分離可以有效的幫助減少鎖競爭,以提升系統性能。
基於AQS的一些同步器
倒計時器 CountDownLatch
這個工具類通常用來控制線程等待,它可以讓某一個線程等待直到倒計時結束,再開始執行。
package Multithread.Test;
import java.util.concurrent.CountDownLatch;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/5/5 0005 17:09
* 允許一個或多個線程等待直到在其他線程中執行的一組操作完成的同步輔助,‘
* 定義一個倒計時器 只有當其他線程中執行的操作完成 這個線程才能繼續執行否則 這個線程一直保持一種阻塞的狀態
* 原理:
* countDownLatch.countDown() //數量-1
* countDownLatch.await() //等待技術器歸零,然後再向下執行
* 每次有線程調用countDown()數量-1,假設計數器變爲0,countDownLatch.await()就會被喚醒,繼續執行!
*
* 這個工具通常用來控制線程等待,它可以讓某一個線程等待直到倒計時結束,再開始執行
*
* 一個線程只有等其他的準備工作做完了才呢個執行 卡在 await()上
*/
public class TestCountDownLatch {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(5);
for(int i=0;i<5;i++){
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"go out");//
countDownLatch.countDown();
System.out.println(123);
}).start();
}
//在這裏設了一到們 ,只有當倒計時爲0時才能執行後虛的操作
//主線程在countDownLatch上等待,當所有檢查任務全部執行完成後,主線程方能繼續執行
countDownLatch.await();
System.out.println("倒計時完成");
}
}
循環柵欄 CylicBarrier
package Multithread.Test;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/5/5 0005 17:24
* 允許一組線程全部等待彼此達到共同屏障點的同步輔助。
* 循環阻塞在涉及固定大小的線程方的程序中很有用,這些線程必須偶爾等待彼此。
* 屏障被稱爲循環 ,因爲它可以在等待的線程被釋放之後重新使用。
*/
public class TestCyclicBarrier {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(6,()->{
System.out.println("召喚神龍成功");
});
for (int i=0;i<6;i++){
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"執行了");
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
System.out.println("主線程執行");
}
}
信號量 Semphore:允許多個線程同時訪問
信號量爲多線程協作提供了更爲強大的控制方法,廣義上來是哦,信號量是對鎖的擴展。無論是內部鎖synchronized還是重入鎖ReentranLock,一次都只允許一個線程範根同一個資源,而信號量卻可以指定多個線程,同時訪問某一資源,信號量主要提供了以下構造函數:
public Semaphore(int permits)
public Semphore(int permits,boolean fair)
在構造信號量對象時,必須要指定信號量的准入數,即同時能申請多少個許可,每個線程每次只能申請許可證
package Multithread.Test;
import java.util.concurrent.Semaphore;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/5/5 0005 17:37
* 一個信號及數量。在概念上,信號量是一組許可證。
* 當信號不夠用的時候,每個acquire()都會阻塞,直到許可證可用,然後才能使用它
*
* 原理:
* semaphore.acquire() 獲得,假設如果已經滿了,等待,等待被釋放爲止
* semaphore.release() 釋放,會將當前的信號量釋放+1,然後喚醒等待的線程
* 作用:多個共享資源互斥的使用,併發限流,控制最大的線程數!
*/
public class TestSemphore {
public static void main(String[] args) {
//3個信號量 ,6個線程來搶
Semaphore semaphore = new Semaphore(3);
for(int i=0;i<6;i++){
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"獲得了資源");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+"執行完畢釋放了資源");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
通過阻塞隊列
線程安全的一些類/容器
除了以上併發包中專有數據結構外,java.util下的Vector是線程安全的,另外Collection工具類可以幫助我們將任意集合包裝成線程安全的集合。
- ConcurrentHashMap:高效的HashMap
- CopyOnWriteArrayList
- ConcurrentLinkedQueue:高效的併發隊列,使用鏈表實現
- Blocking:這是一個接口,JDK內部通過鏈表,數組等方式實現了這個接口,表示阻塞隊列,非常適用於作爲數據共享通道