java併發模型是基於記憶體共享,多執行緒共享變數一般就會涉及到加鎖,這裡介紹幾種降低鎖競爭的方式,最終效果都是降低鎖的顆粒或者降低鎖的競爭次數。
常見鎖優化方式
-
減少鎖的持有時間。
例如對一個方法加鎖不如對其中的同步程式碼行加鎖。
-
讀寫鎖。
可只對鎖操作加鎖,讀不加鎖。這樣讀、讀之間不互斥, 讀、寫和寫、讀互斥,可使用J.U.C中的
ReadWriteLock
。 -
減少鎖顆粒。
如ConcurrentHashMap中對segment加鎖,而不是整個map加鎖。比如map長度為128,分成8份,8份之間不互斥,8份內部才互斥,可以有效降低鎖的競爭。
-
樂觀鎖。
使用CAS演算法,和期待值對比,如果一樣則執行,不一樣則重試等方式。
-
鎖粗化。
如果一個方法有好幾行都是同步程式碼,對這幾行單獨加鎖,不如對這個方法加鎖,可以減少鎖的競爭次數。
場景
服務A和服務B都會呼叫服務C的介面poll,同一個服務呼叫時互斥需要加鎖,不同服務之間呼叫不互斥。
比如A服務有兩個例項A1、A2,B服務有兩個例項B1、B2, A1和B1共同呼叫poll介面時可以並行訪問,A1和A2共同訪問時必須序列執行,
即必須有一個請求執行完才可以執行完下一個。
- 方式一:弱引用+synchronized
弱引用作用可以加快垃圾回收,這裡的服務名很少,所以生成的鎖物件很少可以不使用弱引用。可是其他場景可能要生成的鎖物件有很多,可以使用弱引用加快垃圾回收。
@Component
public class StringLockProvider {
private final Map<Mutex, WeakReference<Mutex>> mutexMap = new WeakHashMap<>();
public Mutex getMutex(String id) {
if (id == null) {
throw new NullPointerException();
}
Mutex key = new MutexImpl(id);
synchronized (mutexMap) {
return mutexMap.computeIfAbsent(key, WeakReference::new).get();
}
}
public interface Mutex {
}
private static class MutexImpl implements Mutex {
private final String code;
private MutexImpl(String id) {
this.code = id;
}
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (this.getClass() == o.getClass()) {
return this.code.equals(o.toString());
}
return false;
}
public int hashCode() {
return code.hashCode();
}
public String toString() {
return code;
}
}
}
@RestController
public class PollController {
@Autowired
private StringLockProvider lockProvider;
/**
* @param service 拉取的服務名
*/
@GetMapping("/v1/data/{service}")
public List<String> poll(@PathVariable("service") String service) {
List<String> data = new ArrayList<>(2 << 4);
synchronized (lockProvider.getMutex(service)) {
//同步程式碼,data.add
}
return data;
}
}
複製程式碼
- 方式二:弱引用+ReentrantLock
此種效果和
弱引用+synchronized
一致, 此處不同主要在於ReentrantLock支援公平鎖, 誰也等待誰先獲取,
而synchronized非公平鎖,隨機選一個獲取鎖,當一個執行緒一直獲取不到鎖,需要等待較長時間,可能造成該介面超時。
@RestController
public class PollController {
private final Map<String, WeakReference<ReentrantLock>> mutexMap = new ConcurrentHashMap<>();
/**
* @param service 拉取的服務名
*/
@GetMapping("/v1/data/{service}")
public List<String> poll(@PathVariable("service") String service) {
List<String> data = new ArrayList<>(2 << 4);
ReentrantLock lock = getReentrantLock(service);
lock.lock();
try {
//同步程式碼。data.add
} finally {
lock.unlock();
}
return data;
}
private ReentrantLock getReentrantLock(String id) {
if (id == null) {
throw new NullPointerException();
}
return mutexMap.computeIfAbsent(id, it -> new WeakReference<>(new ReentrantLock(true))).get();
}
}
複製程式碼
- 方式三:CAS樂觀鎖+循壞
此處使用了while迴圈,使用不當會造成執行緒阻塞,阻塞過多可能會造成當機!
@RestController
public class PollController {
private final Map<String, WeakReference<AtomicBoolean>> atomicMap = new ConcurrentHashMap<>();
/**
* @param service 拉取的服務名
*/
@GetMapping("/v1/data/{service}")
public List<String> poll(@PathVariable("service") String service) {
List<String> data = new ArrayList<>(2 << 4);
AtomicBoolean atomic = getAtomicBoolean(service);
//直到預期的值為true,才會成功,否則迴圈
while (!atomic.compareAndSet(true, false)) {
//Thread.sleep(100)
}
try {
//同步程式碼。data.add
}finally {
atomic.set(true);
}
return data;
}
private AtomicBoolean getAtomicBoolean(String id) {
if (id == null) {
throw new NullPointerException();
}
return atomicMap.computeIfAbsent(id, it -> new WeakReference<>(new AtomicBoolean(true))).get();
}
}
複製程式碼