集合類不安全
List不安全
package unsafe;
import PC.A;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
// ArrayList線程不安全,在多線程下使用ArrayList會(huì)報(bào)錯(cuò):
// java.util.ConcurrentModificationException 并發(fā)修改異常
public class ListTest {
public static void main(String[] args) {
// 并發(fā)下ArrayList 不安全
/**
* 解決方案:
* 1、List<String> list= new Vector<>();
* 實(shí)際上,Vector在jdk1.0就已經(jīng)出來(lái)了,ArrayList在jdk1.2才出來(lái)
* Vector實(shí)際上就是在add時(shí)將方法synchronized鎖起來(lái)了
* 2、List<String> list = Collections.synchronizedList(new ArrayList<>());// 通過(guò)工具類將其轉(zhuǎn)換乘synchronized保證線程安全
* 3、List<String> list = new CopyOnWriteArrayList<>();
*/
//List<String> list = new ArrayList<>();
//List<String> list = Collections.synchronizedList(new ArrayList<>());
List<String> list = new CopyOnWriteArrayList<>();
// CopyOnWrite 寫(xiě)入時(shí)復(fù)制 COW 計(jì)算機(jī)程序設(shè)計(jì)領(lǐng)域的一種優(yōu)化策略
// 多個(gè)線程調(diào)用的時(shí)候 list,讀取的時(shí)候,固定的,寫(xiě)入的時(shí)候可能會(huì)覆蓋
// 在寫(xiě)入的時(shí)候避免覆蓋,造成數(shù)據(jù)問(wèn)題
// CopyOnWriteArrayList比Vector的優(yōu)勢(shì)在于,copyOnWriteArrayList在并發(fā)下效率要比vector更高,因?yàn)镃opyOnWriteArrayList使用的是寫(xiě)時(shí)復(fù)制策略,而非使用synchronized鎖
for (int i = 1; i <= 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
CopyOnWriteArrayList與vector對(duì)比,以下來(lái)自CSDN智能助手的回答:
Java中的CopyOnWriteArrayList和Vector都是線程安全的動(dòng)態(tài)數(shù)組,可以在多線程環(huán)境下使用。
CopyOnWriteArrayList使用了一種特殊的寫(xiě)時(shí)復(fù)制機(jī)制,它在對(duì)數(shù)組進(jìn)行修改時(shí),會(huì)創(chuàng)建一個(gè)新的副本,而不是直接在原數(shù)組上進(jìn)行修改。這樣可以確保在多線程并發(fā)修改時(shí)不會(huì)發(fā)生問(wèn)題。然而,由于每次修改都需要?jiǎng)?chuàng)建一個(gè)新的副本,所以在寫(xiě)入操作頻繁的情況下,CopyOnWriteArrayList的性能可能會(huì)比較低。
Vector是一個(gè)同步的動(dòng)態(tài)數(shù)組,它使用了synchronized關(guān)鍵字來(lái)保證多線程安全。當(dāng)多個(gè)線程同時(shí)修改Vector時(shí),每個(gè)線程會(huì)依次獲得鎖,并按順序執(zhí)行相應(yīng)的修改操作。雖然Vector具有線程安全性,但在高并發(fā)情況下,由于需要頻繁的獲取鎖和釋放鎖,可能會(huì)導(dǎo)致性能下降。
因此,如果在多線程環(huán)境下需要頻繁進(jìn)行讀操作,而寫(xiě)操作較少的情況下,CopyOnWriteArrayList可能會(huì)更適合。而如果需要頻繁進(jìn)行寫(xiě)操作或者對(duì)線程安全性要求不是非常高,可以考慮使用Vector。
而后筆者在IDEA中翻閱關(guān)于CopyOnWriteArralyList的add()方法源碼發(fā)現(xiàn),它還是使用synchronized關(guān)鍵字,源碼如下:
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
synchronized (lock) {
Object[] es = getArray();
int len = es.length;
es = Arrays.copyOf(es, len + 1);
es[len] = e;
setArray(es);
return true;
}
}
然后筆者去查了文心一言的回答,得到的答案依然是CopyOnWriteArralyList沒(méi)有使用synchronized鎖,回答如下:
CopyOnWriteArrayList
的add()
方法不是使用synchronized
關(guān)鍵字來(lái)實(shí)現(xiàn)的。實(shí)際上,CopyOnWriteArrayList
的add()
方法內(nèi)部使用的是一種稱為"寫(xiě)時(shí)復(fù)制"(Copy-on-Write)的技術(shù)。在寫(xiě)時(shí)復(fù)制策略中,數(shù)據(jù)結(jié)構(gòu)被修改時(shí),修改操作會(huì)在一個(gè)新的副本上進(jìn)行,而不是直接在原始數(shù)據(jù)上進(jìn)行。這樣,讀取操作可以在原始數(shù)據(jù)上進(jìn)行,而不需要等待寫(xiě)入操作完成。寫(xiě)入操作完成后,原始數(shù)據(jù)會(huì)被丟棄,新的副本會(huì)成為新的原始數(shù)據(jù)。
在
CopyOnWriteArrayList
中,當(dāng)調(diào)用add()
方法時(shí),它會(huì)創(chuàng)建一個(gè)新的數(shù)組副本,將新元素添加到該副本中,然后將原始數(shù)組引用指向新數(shù)組。由于創(chuàng)建新數(shù)組和修改操作是在一個(gè)單獨(dú)的寫(xiě)鎖上進(jìn)行的,因此多個(gè)線程可以同時(shí)讀取原始數(shù)組,而不會(huì)看到正在進(jìn)行的寫(xiě)入操作。因此,
CopyOnWriteArrayList
的add()
方法并沒(méi)有使用synchronized
關(guān)鍵字來(lái)實(shí)現(xiàn)線程安全。而是使用寫(xiě)時(shí)復(fù)制技術(shù)和讀寫(xiě)鎖來(lái)實(shí)現(xiàn)更細(xì)粒度的并發(fā)控制。
因此,copyWriteArrayList在并發(fā)下是否效率更高有待商榷
Set不安全
package unsafe;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
public class SetTest {
public static void main(String[] args) {
// Set<String> set = new HashSet<>(); // 線程不安全
// Set<String> set = Collections.synchronizedSet(new HashSet<>());// 通過(guò)工具類將其轉(zhuǎn)換成synchronized保證線程安全
Set<String> set = new CopyOnWriteArraySet<>();// 通過(guò)讀寫(xiě)復(fù)制策略保證線程安全
for (int i = 1; i <= 20; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
hashSet的底層是什么?
HashSet本質(zhì)上就是HashMap,源碼:
/**
* Constructs a new, empty set; the backing {@code HashMap} instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}
// add() set本質(zhì)就是map key是無(wú)法重復(fù)的
/**
* Adds the specified element to this set if it is not already present.
* More formally, adds the specified element {@code e} to this set if
* this set contains no element {@code e2} such that
* {@code Objects.equals(e, e2)}.
* If this set already contains the element, the call leaves the set
* unchanged and returns {@code false}.
*
* @param e element to be added to this set
* @return {@code true} if this set did not already contain the specified
* element
*/
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
// PRESENT 是一個(gè)常量
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
HashMap不安全
ConcurrentHashMap<>()
package unsafe;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class MapTest {
public static void main(String[] args) {
// 線程不安全
// map 是下面這條語(yǔ)句這樣用的碼?----> 不是,工作中不用HashMap
// 默認(rèn)等價(jià)于什么? -----> Map<String, String> map = new HashMap<>(16,0.75);
// Map<String, String> map = new HashMap<>();
// 線程安全
Map<String,String> map = new ConcurrentHashMap<>();
for (int i = 1; i <= 30; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
Callable
特點(diǎn)
1、可以有返回值
2、可以拋出異常
3、方法不同,run() / call()
package callable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// new Thread(new Runnable()).start();
// 等價(jià)于 new Thread(new FutureTask<V>()).start();
// new Thread(new FutureTask<V>(Callable)).start();--->FutureTask<V>的構(gòu)造器為Callable
new Thread().start();// 怎么啟動(dòng)callable
MyThread thread = new MyThread();
FutureTask futureTask = new FutureTask(thread);// 適配類
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start();// 結(jié)果會(huì)被緩存,提高效率
// 這個(gè)get方法可能會(huì)產(chǎn)生阻塞,加入call()是一個(gè)耗時(shí)操作,則get需要等待返回值
// 一般將get放在最后,或者使用異步通信來(lái)處理
Integer o = (Integer) futureTask.get();// 獲取Callable的返回結(jié)果
System.out.println(o);
}
}
class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("call()");
return 1024;
}
}
細(xì)節(jié):
1、 有緩存文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-724644.html
2、結(jié)果可能需要等待,會(huì)阻塞文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-724644.html
到了這里,關(guān)于JUC并發(fā)編程——集合類不安全及Callable(基于狂神說(shuō)的學(xué)習(xí)筆記)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!