자바 동기 용기 와 병발 용기 상세 설명
자바 에서 동기 화 용 기 는 주로 2 가지 유형 을 포함한다.
Stack 도 동기 용기 입 니 다.동기 화 된 방법 도 synchronized 로 동기 화 되 었 습 니 다.이것 은 실제로 Vector 류 에 계승 되 었 습 니 다.
HashTable 은 Map 인 터 페 이 스 를 실 현 했 습 니 다.HashMap 과 비슷 하지만 HashTable 은 동기 화 처 리 를 했 고 HashMap 은 없습니다.
동기 용기 결함
동기 용기 의 동기 화 원 리 는 방법 적 으로 synchronized 로 수식 하 는 것 이다.그렇다면 이 방법 들 은 매번 하나의 스 레 드 만 호출 할 수 있다.
성능 문제
synchronized 에 의 해 수 정 된 방법 으로 매번 하나의 스 레 드 만 실행 할 수 있 기 때문에 이 방법 에 접근 하려 는 다른 스 레 드 는 기다 릴 수 밖 에 없습니다.분명히 이런 방식 은 synchronized 를 사용 하지 않 은 용기 보다 성능 이 떨어진다.
안전 문제
동기 화 용 기 는 정말 안전 합 니까?
꼭 그렇지 는 않다.동기 용기 가 꼭 안전 한 것 은 아니다.복합 작업 을 할 때,여전히 자 물 쇠 를 넣 어 보호해 야 한다.
흔히 볼 수 있 는 복합 조작 은 다음 과 같다.
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">public class Test { static Vector<Integer> vector = new Vector<Integer>();
public static void main(String[] args) throws InterruptedException {
while(true) {
for (int i=0;i<10;i++)
vector.add(i);
Thread thread1 = new Thread(){
public void run() {
for (int i=0;i<vector.size();i++)
vector.remove(i);
}
;
}
;
Thread thread2 = new Thread(){
public void run() {
for (int i=0;i<vector.size();i++)
vector.get(i);
}
;
}
;
thread1.start();
thread2.start();
while(Thread.activeCount()>10) {
}
}
}
}
</pre>
실행 중 배열 의 크로스 오 버 가 발생 할 수 있 습 니 다.Vector 는 스 레 드 가 안전 한데 왜 이 잘못 을 보고 합 니까?간단 합 니 다.Vector 에 대해 서 는 매 시간 하나의 스 레 드 만 접근 할 수 있 도록 보장 할 수 있 지만 이러한 가능성 을 배제 하지 않 습 니 다.
어느 스 레 드 가 어느 순간 이 문장 을 실행 할 때:
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">for (int i=0;i<vector.size();i++)
vector.get(i);
</pre>
만약 이때 vector 의 size 방법 이 10 을 되 돌려 준다 면 i 의 값 은 9 입 니 다.그리고 다른 라인 에서 이 문장 을 실 행 했 습 니 다.
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">for (int i=0;i<vector.size();i++)
vector.remove(i);
</pre>
다음 9 로 표 시 된 요 소 를 삭 제 했 습 니 다.그러면 get 방법 을 통 해 9 로 표 시 된 요 소 를 방문 하면 문제 가 생 길 것 입 니 다.
안전 예시
따라서 스 레 드 안전 을 확보 하기 위해 서 는 방법 호출 단 에서 추가 적 인 동기 화 조 치 를 취해 야 합 니 다.아래 와 같 습 니 다.
public class Test {
static Vector<Integer> vector = new Vector<Integer>();
public static void main(String[] args) throws InterruptedException {
while(true) {
for (int i=0;i<10;i++)
vector.add(i);
Thread thread1 = new Thread(){
public void run() {
synchronized (Test.class) {
//
for (int i=0;i<vector.size();i++)
vector.remove(i);
}
}
;
}
;
Thread thread2 = new Thread(){
public void run() {
synchronized (Test.class) {
for (int i=0;i<vector.size();i++)
vector.get(i);
}
}
;
}
;
thread1.start();
thread2.start();
while(Thread.activeCount()>10) {
}
}
}
}
ConcurrentModificationException 이상Vector 등 용 기 를 동시에 교체 수정 할 때 Concurrent ModificationException 이상 을 보고 하고 이 이상 에 대해 서 는 후속 글 에서 설명 할 예정 이다.
하지만 병발 용기 에 서 는 이 문제 가 발생 하지 않 는 다.
병발 용기
JDK 의 java.util.concurrent 패키지(즉 jc)에서 매우 유용 한 병발 용 기 를 제공 합 니 다.
요점
역할:Concurrent HashMap 은 라인 이 안전 한 HashMap 입 니 다.
원리:JDK 6 와 JDK 7 에서 Concurrent HashMap 은 세그먼트 잠 금 체 제 를 사용 했다.JDK 8 에 서 는 잠 금 세그먼트 메커니즘 을 지양 하고 CAS 알고리즘 을 활용 한 것 으로 변경 했다.
소스 코드
JDK7
Concurrent HashMap 류 가 jdk 1.7 에서 디자인 되 었 는데 그 기본 구 조 는 그림 과 같다.
모든 segment 는 HashEntry
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
implements ConcurrentMap<K, V>, Serializable { // hashmap map, segment ; hashtable , put, remove , , // ,
final Segment<K,V>[] segments;
// Segment hashmap, table , ReentrantLock,
static final class Segment<K,V> extends ReentrantLock implements Serializable {
transient volatile HashEntry<K,V>[] table;
transient int count;
}
// , Key, Value
static final class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next;
}
}
</pre>
JDK8jdk 8 에서 주로 2 가지 개선 을 했 습 니 다.
그러나 실제 상황 이 항상 이상 적 인 것 은 아 닙 니 다.Concurrent HashMap 류 의 기본 로드 인 자 는 0.75 이지 만 데이터 양 이 너무 많 거나 운 이 좋 지 않 은 상황 에서 일부 대기 행렬 의 길이 가 너무 긴 상황 이 존재 합 니 다.만약 에 단 방향 목록 방식 을 사용한다 면 특정한 노드 의 시간 복잡 도 는 O(n)입 니 다.
따라서 개수 가 8(기본 값)이 넘 는 목록 에 대해 jdk 1.8 에서 붉 은 검 은 나무의 구 조 를 사용 하면 조회 시간 복잡 도 를 O(logN)로 낮 추어 성능 을 개선 할 수 있다.
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">final V putVal(K key, V value, Boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f;
int n, i, fh;
// table , ; , hash i, tab[i] , Node 。 :tab[i] 。
if (tab == null || (n = tab.length) == 0)
tab = initTable(); else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break;
// no lock when adding to empty bin
}
// tab[i] hash MOVED, transfer , table。 else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f); else {
V oldVal = null;
// , segment,
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
// key e, e.val = value 。
if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
// key , Node 。
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
// TreeBin , , putTreeVal 。 else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
// >=8, 。
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null) return oldVal;
break;
}
}
}
// 1, transfer ( )。
addCount(1L, binCount);
return null;
}
</pre>
예시
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">public class ConcurrentHashMapDemo { public static void main(String[] args) throws InterruptedException { // HashMap ConcurrentModificationException // Map<Integer, Character> map = new HashMap<>();
Map<Integer, Character> map = new ConcurrentHashMap<>();
Thread wthread = new Thread(() -> {
System.out.println(" "); for (int i = 0; i < 26; i++) {
map.put(i, (char) ('a' + i));
}
});
Thread rthread = new Thread(() -> {
System.out.println(" "); for (Integer key : map.keySet()) {
System.out.println(key + " - " + map.get(key));
}
});
wthread.start();
rthread.start();
Thread.sleep(1000);
}
}</pre>
CopyOnWriteArrayList요점
역할:CopyOnWrite 는 쓰기 시 복사 한 다 는 뜻 입 니 다.CopyonWriteArrayList 는 라인 이 안전 한 ArrayList 입 니 다.
원리:
소스 코드
중요 속성
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">
/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;
</pre>
중요 한 방법추가 작업
추 가 된 논 리 는 간단 합 니 다.먼저 원 용기 copy 1 부 를 한 다음 새 복사 본 에 쓰기 작업 을 한 다음 인용 을 전환 합 니 다.물론 이 과정 은 자 물 쇠 를 채 워 야 한다.
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">public Boolean add(E e) {
//ReentrantLock ,
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
// ,
Object[] newElements = Arrays.copyOf(elements, len + 1);
//
newElements[len] = e;
//
setArray(newElements);
return true;
}
finally {
//
lock.unlock();
}
}
</pre>
삭제 작업삭제 작업 은 같은 이치 입 니 다.요 소 를 삭제 할 것 을 제외 한 다른 요 소 를 새 복사 본 에 복사 한 다음 인용 을 전환 하여 원래 용기 의 인용 을 새 복사 본 으로 가 리 킵 니 다.같은 쓰기 동작 이 므 로 자 물 쇠 를 추가 해 야 합 니 다.
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">public E remove(int index) {
//
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0) // , len-1 ,
setArray(Arrays.copyOf(elements, len - 1)); else {
// , ,
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
}
finally {
//
lock.unlock();
}
}
</pre>
읽 기 동작CopyonWriteArray List 의 읽 기 동작 은 잠 금 을 넣 지 않 아 도 되 고 성능 이 매우 높다.
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
</pre>
예시
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">public class CopyOnWriteArrayListDemo { static class ReadTask implements Runnable {
List<String> list;
ReadTask(List<String> list) {
this.list = list;
}
public void run() {
for (String str : list) {
System.out.println(str);
}
}
}
static class WriteTask implements Runnable {
List<String> list;
int index;
WriteTask(List<String> list, int index) {
this.list = list;
this.index = index;
}
public void run() {
list.remove(index);
list.add(index, "write_" + index);
}
}
public void run() {
final int NUM = 10;
// ArrayList ConcurrentModificationException // List<String> list = new ArrayList<>();
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < NUM; i++) {
list.add("main_" + i);
}
ExecutorService executorService = Executors.newFixedThreadPool(NUM);
for (int i = 0; i < NUM; i++) {
executorService.execute(new ReadTask(list));
executorService.execute(new WriteTask(list, i));
}
executorService.shutdown();
}
public static void main(String[] args) {
new CopyOnWriteArrayListDemo().run();
}
}
</pre>
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Is Eclipse IDE dying?In 2014 the Eclipse IDE is the leading development environment for Java with a market share of approximately 65%. but ac...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.