JAVA併發之多線程基礎(5)

NO IMAGE

JAVA併發之多線程基礎(5)

上面介紹了併發編程中的柵欄等JAVA併發之多線程基礎(4)
。通過唯一的一個終點線來幫助確定線程是多晚開始執行下一次操作。

LockSupport

提供了一個比較底層的線程掛起操作。有點類似於suspend()方法,但是這個方法不建議使用。

  1. park()使得當前線程掛起。裡面也是調用了Unsafe類進行底層操作。
public static void park() {
UNSAFE.park(false, 0L);
}

2.unpark(Thread thread)是將當前線程繼續往下執行。

public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}

LockSupport上面的兩個方法不像suspend()resume()方法。在這兩個方法中resume()必須在suspend()之前執行。否則線程就會被永遠的掛起,造成死鎖。而LockSupport中的unpark可以在park之前。

package com.montos.lock;
import java.util.concurrent.locks.LockSupport;
public class LockSupportDemo {
public static Object obj = new Object();
static ChangeObjectThread t1 = new ChangeObjectThread("t1");
static ChangeObjectThread t2 = new ChangeObjectThread("t2");
public static class ChangeObjectThread extends Thread {
public ChangeObjectThread(String name) {
super(name);
}
@Override
public void run() {
synchronized (obj) {
System.out.println("in " + getName());
LockSupport.park();
}
}
}
public static void main(String[] args) throws InterruptedException {
t1.start();
Thread.sleep(100);
t2.start();
LockSupport.unpark(t1);
LockSupport.unpark(t2);
t1.join();
t2.join();
}
}

通過上面的一個小的Demo可以看出LockSupport的使用方法,在使用方法中也是很簡單的。以上篇幅就對JDK內的併發類進行了講解。大多數用到了CAS無鎖操作。避免線程阻塞的情況發生。

併發集合

在日常的業務處理中,集合類是我們使用最多的一個操作。對應的也就是三大類:MapSet以及最後的List。在日常併發量小的情況下,也許我們會這麼使用對應的集合操作:

//Map集合
private static final Map<String,String> map = Collections.synchronizedMap(new HashMap<String,String>());
//List集合	
private static final List<String> list = Collections.synchronizedList(new ArrayList<String>());
//Set集合
private static final Set<String> set = Collections.synchronizedSet(new HashSet<>());

上面的使用了Collections中的同步方法進行包裝。深入瞭解裡面的調用過程,也就是各個方法上面加上了synchronized進行限制。從而達到最後線程安全的目的。如果併發量很大的話,是很會影響性能的,畢竟它使得對應的讀和寫操作都變成了串行。於是有了下面的幾種線程安全類:

  • ConcurrentHashMap高性能併發的Map。裡面的操作是跟HashMap是一樣的。但是裡面多了一種結構:Segment(段)。
static class Segment<K,V> extends ReentrantLock implements Serializable {
private static final long serialVersionUID = 2249069246763182397L;
final float loadFactor;
Segment(float lf) { this.loadFactor = lf; }
}

可以看出他是繼承了重入鎖進行實現的。為什麼會有段的概念在這裡面呢?為了更好的支持高併發而設計的一個概念。每個元素在特定的段中,將整個集合分成許多段進行管理,而高併發的時候,只要對每個段進行控制就能起到一個同步的作用。

再統計數量大小的時候,ConcurrentHashMap中利用了CounterCell結構體進行統計。每一個段中存儲了該段的大小,然後再循環求和出當前的數組大小情況。

final long sumCount() {
CounterCell[] as = counterCells; CounterCell a;
long sum = baseCount;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
  • BlockingQueue是一個非常好的多線程共享數據的容器。當隊列為空的時候,讀線程就會進行等待,相反,如果當前隊列已滿,那麼寫線程就會等待。看到對應的實現類中都會有下面三個成員變量:
/** Main lock guarding all access */
final ReentrantLock lock; //加鎖進行控制訪問
/** Condition for waiting takes */
private final Condition notEmpty;//元素獲取的時候,進行判斷是否為空進行阻塞
/** Condition for waiting puts */
private final Condition notFull;//元素加入的時候,判斷隊列是否已滿

這上面的三個變量就控制整個阻塞隊列的訪問以及元素的增加。從根本上來說他並不像上面介紹的ConcurrentHashMap訪問性能高,但是我們需要的是他的多線程訪問共有數據的能力。

阻塞隊列的元素獲取方法

public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();//獲取可中斷鎖
try {
while (count == 0)
notEmpty.await();//隊列為空,進行讀線程等待
return dequeue();
} finally {
lock.unlock();
}
}
 private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();//通知寫線程進行寫入操作
return x;
}

阻塞隊列的元素增加方法

public void put(E e) throws InterruptedException {
checkNotNull(e);//判斷元素是否為空
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();//獲取可中斷鎖
try {
while (count == items.length)
notFull.await();//隊列滿時,進行寫線程等待
enqueue(e);
} finally {
lock.unlock();
}
}
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();//通知讀線程進行操作 
}
  • ConcurrentLinkedQueue上面談及到的是多線程共享數據的容器,而這個是高併發情況下使用的。裡面大量的使用了無鎖的操作以及鎖自旋,可以很好的保證性能問題,有興趣的小夥伴可以去了解下。

以上談及到的就是JDK中關於併發的一些簡單介紹。在我介紹完成之後,我會對底層的源碼進一步講解,有興趣的小夥伴可以關注我~

相關文章

JAVA中volatile介紹

JAVA鎖介紹

JAVA操作碼相關指令介紹

JAVA併發之多線程基礎(6)