Java的面向?qū)ο缶幊?/a>">Java的面向?qū)ο缶幊?/a>
727
2025-03-31
不可變對象、同步容器、juc并發(fā)容器
(1)不可變對象:
不可變對象需要滿足的條件
對象創(chuàng)建以后其狀態(tài)就不能修改
對象所有域都是final類型
對象時正確創(chuàng)建(在對象創(chuàng)建期間,this引用沒有溢出)
final 關(guān)鍵字:類、方法、變量
修飾類:不能被繼承
修飾方法:鎖定方法不能被繼承類修改 ,效率
修飾變量:基本數(shù)據(jù)類型變量,引用類型變量
@Slf4j
public class ImmutableExample1 {
private final static Integer a = 1;
private final static String b = "2";
private final static Map
static {
map1.put(1, 2);
map1.put(3, 4);
map1.put(5, 6);
}
private static Map
private static List
static {
map2.put(1, 2);
map2.put(3, 4);
map2.put(5, 6);
//通過Collections獲取一個不可被修改的map
map2 = Collections.unmodifiableMap(map2);
list2.add(1);
list2.add(2);
//通過Collections獲取一個不可被修改的list
list2 = Collections.unmodifiableList(list2);
}
//通過guava 也可以獲取不可變list,set ,map
private static final ImmutableList
private static final List
private static final ImmutableMap
private static final ImmutableMap
static {
list4.add(1);
list4.add(2);
}
public static void main(String[] args) {
// a = 2 ;
// b ="3";
// map =Maps.newHashMap();
map1.put(1, 3);
log.info("{}", map1.get(1));
map2.put(1, 3);
list2.add(4);
log.info("{}", map2.get(1));
list3.add(56);
ImmutableSet list2 = ImmutableSet.copyOf(list4);
list2.add(7);
unMap.put(1, 6);
unMap1.put(1, 8);
}
}
(2) ? 線程 封閉
public class RequestHolder {
private final static ThreadLocal
public static void add(Long id) {
requestHolder.set(id);
}
public static Long getId() {
return requestHolder.get();
}
public static void remove() {
requestHolder.remove();
}
}
(3)常見的線程不安全的類
public class StringExample1 {
//請求次數(shù)
private static int clientTotal = 5000;
//允許同時運(yùn)行的線程數(shù)
private static int threadTotal = 200;
/**
* stringBuilder 線程不安全
* stringBuffer 線程安全
*/
//public static StringBuilder stringBuilder=new StringBuilder();
public static StringBuffer stringBuffer = new StringBuffer();
/**
* simpleDateFormat 不是線程安全的
* joda-time 的dateTimeFormatter 是線程安全的
*/
public static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd");
public static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyy/MM/dd");
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
append();
semaphore.release();
} catch (InterruptedException e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.shutdown();
log.info("count:{}", stringBuffer.length());
}
private static void append() {
DateTime dateTime = DateTime.parse("2018/06/07", dateTimeFormatter);
log.info(dateTime.toDate().toString());
}
}
(4)同步容器
@Slf4j
public class ContainExample {
//請求次數(shù)
private static int clientTotal = 5000;
//允許同時運(yùn)行的線程數(shù)
private static int threadTotal = 200;
private static List
private static Vector
static {
vector.add(1);
vector.add(2);
vector.add(3);
}
private static List
private static Set
private static Set
private static Map
private static Map
private static Map
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal; i++) {
final int index = i;
executorService.execute(() -> {
try {
semaphore.acquire();
putValue(index);
semaphore.release();
} catch (InterruptedException e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.shutdown();
log.info("count:{}", safeMap.size());
// testVector();
testVector1();
}
private static void putValue(int i) {
safeMap.put(i,i);
}
public static void testVector(){
//ExecutorService executorService = Executors.newCachedThreadPool();
//在這種情況下 線程的 vector 也會變得線程不安全
while(true) {
new Thread(() -> {
for (int i = 0, l = vector.size(); i < l; i++) {
vector.remove(i);
}
}).start();
new Thread(() -> {
for (int i = 0, l = vector.size(); i < l; i++) {
vector.get(i);
}
}).start();
}
}
public static void testVector1(){
try {
for (Integer i : vector) { //不推薦
if(i == 3){
vector.remove(i);
}
}
}catch (Exception e){
log.error("foreach循環(huán)刪除時報錯",e);
}
try {
Iterator
while(iterator.hasNext()){ //推薦
if(iterator.next() == 3){
iterator.remove();
}
}
}catch (Exception e){
log.error("iterator循環(huán)刪除時報錯",e);
}
try {
for (int i =0; i< vector.size();i++) { //推薦
if(i == 3){
vector.remove(i);
}
}
}catch (Exception e){
log.error("for循環(huán)刪除時報錯",e);
}
}
}
(5)并發(fā)容器
CopyOnWriteArrayList
CopyOnWriteArrayList是ArrayList的一個線程安全的變體,其中所有可變操作(add、set等等)都是通過對底層數(shù)組進(jìn)行一次新的復(fù)制來實(shí)現(xiàn)的。與ArrayList不同處就在于是否會拷貝數(shù)組和加鎖.
CopyOnWriteArrayList顧名思義就是寫時復(fù)制的ArrayList,其意思就是在修改容器的元素時,并不是直接在原數(shù)組上修改,而是先拷貝了一份數(shù)組,然后在拷貝的數(shù)組上進(jìn)行修改,修改完后將其引用賦值給原數(shù)組的引用。這樣體現(xiàn)了讀寫分離,這樣無論在任何時候我們都可以對容器進(jìn)行讀取。
所謂動態(tài)數(shù)組操作機(jī)制:即通過volatile修飾的Object類型數(shù)組來進(jìn)行數(shù)組的CRUD操作。在進(jìn)行add,set,remove等可變操作的時候,都會先新建一個數(shù)組把更新的值賦給該數(shù)組,然后再傳遞給上面的array數(shù)組來保持該次操作的可見性。這也是CopyOnWriteArrayList命名的由來。這一般需要很大的開銷,但是當(dāng)遍歷操作的數(shù)量大大超過可變操作的數(shù)量時,即在進(jìn)行讀操作時的效率要遠(yuǎn)遠(yuǎn)高于寫或是修改操作,這種方法可能比其他替代方法更 有效。
CopyOnWriteArraySet
它是線程安全的無序的集合,可以將它理解成線程安全的HashSet。有意思的是,CopyOnWriteArraySet和HashSet雖然都繼承于共同的父類AbstractSet;但是,HashSet是通過“散列表(HashMap)”實(shí)現(xiàn)的,而CopyOnWriteArraySet則是通過“動態(tài)數(shù)組(CopyOnWriteArrayList)”實(shí)現(xiàn)的,并不是散列表。
和CopyOnWriteArrayList類似,CopyOnWriteArraySet具有以下特性:
1. 它最適合于具有以下特征的應(yīng)用程序:Set 大小通常保持很小,只讀操作遠(yuǎn)多于可變操作,需要在遍歷期間防止線程間的沖突。
2. 它是線程安全的。
3. 因為通常需要復(fù)制整個基礎(chǔ)數(shù)組,所以可變操作(add()、set() 和 remove() 等等)的開銷很大。
4. 迭代器支持hasNext(), next()等不可變操作,但不支持可變 remove()等 操作。
5. 使用迭代器進(jìn)行遍歷的速度很快,并且不會與其他線程發(fā)生沖突。在構(gòu)造迭代器時,迭代器依賴于不變的數(shù)組快照。
ConcurrentSkipListSet
ConcurrentSkipListSet是線程安全的有序的集合,適用于高并發(fā)的場景。
ConcurrentSkipListSet和TreeSet,它們雖然都是有序的集合。但是,第一,它們的線程安全機(jī)制不同,TreeSet是非線程安全的,而ConcurrentSkipListSet是線程安全的。第二,ConcurrentSkipListSet是通過ConcurrentSkipListMap實(shí)現(xiàn)的,而TreeSet是通過TreeMap實(shí)現(xiàn)的。
ConcurrentHashMap
ConcurrentHashMap是線程安全且高效的HashMap。正常業(yè)務(wù)場景中,我們會經(jīng)常會用到HashMap,而在多線程環(huán)境下,Java.util.Hashmap進(jìn)行put操作時會導(dǎo)致死循環(huán),是因為多線程會導(dǎo)致HashMap的Entry鏈表形成環(huán)形數(shù)據(jù)結(jié)構(gòu),一旦形成環(huán)形數(shù)據(jù)結(jié)構(gòu),Entry的next節(jié)點(diǎn)永遠(yuǎn)不為空,就會死循環(huán)獲取Entry。HashMap put時,發(fā)生死循環(huán)的原因是因為rehash時導(dǎo)致
而線程安全的HashTable 使用synchronized來保證線程安全,在線程鎖競爭激烈的情況下 HashTable的效率非常低下。在Hashtable里,同一把鎖連get都會使用synchronized來保證線程安全,Hashtable會競爭同一把鎖,所以效率低下。若是能夠變成多把鎖,就能有效提升并發(fā)的效率。ConcurrentHashMap采用了鎖分段技術(shù),并且設(shè)計與實(shí)現(xiàn)非常精巧,大量的利用了volatile,final,CAS等lock-free技術(shù)來減少鎖競爭對于性能的影響
ConcurrentSkipListMap
ConcurrentSkipListMap提供了一種線程安全的并發(fā)訪問的排序映射表。內(nèi)部是SkipList(跳表)結(jié)構(gòu)實(shí)現(xiàn),在理論上能夠在O(log(n))時間內(nèi)完成查找、插入、刪除操作。
SkipList是一種紅黑樹的替代方案,由于SkipList與紅黑樹相比無論從理論和實(shí)現(xiàn)都簡單許多,所以得到了很好的推廣。SkipList是基于一種統(tǒng)計學(xué)原理實(shí)現(xiàn)的,有可能出現(xiàn)最壞情況,即查找和更新操作都是O(n)時間復(fù)雜度,但從統(tǒng)計學(xué)角度分析這種概率極小。
使用SkipList類型的數(shù)據(jù)結(jié)構(gòu)更容易控制多線程對集合訪問的處理,因為鏈表的局部處理性比較好,當(dāng)多個線程對SkipList進(jìn)行更新操作(指插入和刪除)時,SkipList具有較好的局部性,每個單獨(dú)的操作,對整體數(shù)據(jù)結(jié)構(gòu)影響較小。而如果使用紅黑樹,很可能一個更新操作,將會波及整個樹的結(jié)構(gòu),其局部性較差。因此使用SkipList更適合實(shí)現(xiàn)多個線程的并發(fā)處理。
在非多線程的情況下,應(yīng)當(dāng)盡量使用TreeMap。此外對于并發(fā)性相對較低的并行程序可以使用Collections.synchronizedSortedMap將TreeMap進(jìn)行包裝,也可以提供較好的效率。對于高并發(fā)程序,應(yīng)當(dāng)使用ConcurrentSkipListMap,能夠提供更高的并發(fā)度。
所以在多線程程序中,如果需要對Map的鍵值進(jìn)行排序時,請盡量使用ConcurrentSkipListMap,可能得到更好的并發(fā)度。
注意,調(diào)用ConcurrentSkipListMap的size時,由于多個線程可以同時對映射表進(jìn)行操作,所以映射表需要遍歷整個鏈表才能返回元素個數(shù),這個操作是個O(log(n))的操作。
在JDK1.8中,ConcurrentHashMap的性能和存儲空間要優(yōu)于ConcurrentSkipListMap,但是ConcurrentSkipListMap有一個功能:?它會按照鍵的自然順序進(jìn)行排序。
總結(jié):
線程限制 : 一個被線程限制的對象,由線程獨(dú)占,并且只能被占有它的線程修改
共享只讀:一個共享只讀的對象,在沒有額外同步的情況下,可以被多個線程并發(fā)訪問,但是
任何線程都不能修改它
線程安全對象: 一個線程安全的對象或者容器,在內(nèi)部通過同步機(jī)制保證線程安全,所以其他線程
無需額外的同步就可以通過公共接口隨意訪問它
被守護(hù)對象:被守護(hù)對象只能通過獲取特定的鎖來訪問
Java 任務(wù)調(diào)度 容器 數(shù)據(jù)結(jié)構(gòu)
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。