Heim > Fragen und Antworten > Hauptteil
Ist das Verhältnis von „lock-free“ und „locked“ im folgenden Code eine bessere Implementierung? Ich verwende jmeter, um 20 Anfragen pro Sekunde zu stellen. Der größte Teil der Ausgabe des Schlafvorgangs bei der Ausführung von sperrfreiem Code test()
unterscheidet sich erheblich von 500 Millisekunden, während die Ausgabe des gesperrten Codes grundsätzlich 500 Millisekunden beträgt, mit einer Differenz von 1 oder 2 Millisekunden. Dieses Problem ist sehr seltsam
@Controller
@RequestMapping("/bench/")
public class BenchController {
@Autowired
private FlowService flowService;
private static Object[] lockObj;
private static AtomicReference<Integer>[] locks;
static {
lockObj = new Object[100];
for (int i = 0; i < lockObj.length; i++) {
lockObj[i] = new Object();
}
locks = new AtomicReference[100];
for (int i = 0; i < locks.length; i++) {
locks[i] = new AtomicReference<Integer>(null);
}
}
@RequestMapping("a")
@ResponseBody
public long a(int id) throws Exception {
long start = System.currentTimeMillis();
int index = id % 100;
long inner=0;
synchronized (lockObj[index]) {
inner=test();
}
long result = System.currentTimeMillis() - start;
System.out.println("all: "+result+" inner: "+inner);
return result;
}
@RequestMapping("b")
@ResponseBody
public long b(int id) throws Exception {
long start = System.currentTimeMillis();
AtomicReference<Integer> lock=locks[id % 100];
while (!lock.compareAndSet(null, id)) {}
long inner=test();
boolean flag=lock.compareAndSet(id, null);
long result = System.currentTimeMillis() - start;
System.out.println("all: "+result+" inner: "+inner+" flag:"+flag);
return result;
}
public long test()throws Exception{
long innerstart = System.currentTimeMillis();
Thread.sleep(500);
System.out.println(System.currentTimeMillis()-innerstart);
return System.currentTimeMillis()-innerstart;
}
}
曾经蜡笔没有小新2017-05-17 10:03:02
1.首先,明确两个问题,synchronized 一般不是跟AtomicXX类进行比较,更多的是跟ReentrantLock这个类进行比较,网上关于这2者的比较很多,可以自行google之。
2.问题中关于无锁跟有锁的疑问,测试代码b中的代码是有问题的,
对于方法a,synchronized代码块来说,锁被第一个进来的线程持有后,后续线程请求获取锁会被阻塞挂起,直到前面一个线程释放锁,后续的线程会恢复执行,由于锁的存在,20个请求类似于顺序执行,这一层由jvm调度
对于方法b,cas操作是非阻塞的,方法中的while循环其实是一直在执行(不断尝试进行cas操作),而我们知道,死循环是会消耗cpu资源的,并发数越多,线程越多,此处的cas操作越多,必然导致cpu使用率飙升,方法b中的代码由jmeter测试的时候理论上来说应该一直由20个活跃的工作线程存在,cpu与线程模型是另外一个话题,线程数的调优是jvm一个比较高级的话题,感兴趣可以自行google之
说说ReentrantLock与synchronized:通常情况下在高并发下,ReentrantLock比synchronized拥有更好的性能,而且ReentrantLock提供来一些synchronized并不提供的功能(锁超时自动放弃等),示例代码中可以减少sleep的时间,从而模拟更短停顿,更高的并发,500ms对于人来说很短,对于cpu来说基本就是天文数字了,基本用“慢如蜗牛”来形容也不为过,修改类一下示例代码:
package com.gzs.learn.springboot;
import java.util.LinkedList;
import java.util.Random;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/bench/")
public class BenchController {
private Random random = new Random();
private static Object[] lockObj;
private static AtomicReference<Integer>[] locks;
private static ReentrantLock[] reentrantLocks;
static {
lockObj = new Object[100];
for (int i = 0; i < lockObj.length; i++) {
lockObj[i] = new Object();
}
locks = new AtomicReference[100];
for (int i = 0; i < locks.length; i++) {
locks[i] = new AtomicReference<Integer>(null);
}
reentrantLocks = new ReentrantLock[100];
for (int i = 0; i < reentrantLocks.length; i++) {
reentrantLocks[i] = new ReentrantLock();
}
}
@RequestMapping("a/{id}")
@ResponseBody
public long a(@PathVariable("id") int id) throws Exception {
long start = System.currentTimeMillis();
int index = id % 100;
long inner = 0;
synchronized (lockObj[index]) {
inner = test();
}
long result = System.currentTimeMillis() - start;
System.out.println("all: " + result + " inner: " + inner);
return result;
}
@RequestMapping("b/{id}")
@ResponseBody
public long b(@PathVariable("id") int id) throws Exception {
long start = System.currentTimeMillis();
id = id % 100;
AtomicReference<Integer> lock = locks[id];
int b = 0;
while (!lock.compareAndSet(null, id)) {
b = 1 + 1;
}
long inner = test();
boolean flag = lock.compareAndSet(id, null);
long result = System.currentTimeMillis() - start;
System.out.println("all: " + result + " inner: " + inner + " flag:" + flag);
System.out.println(b);
return result;
}
@RequestMapping("c/{id}")
@ResponseBody
public long c(@PathVariable("id") int id) throws Exception {
long start = System.currentTimeMillis();
id = id % 100;
ReentrantLock lock = reentrantLocks[id];
lock.lock();
long inner = test();
lock.unlock();
long result = System.currentTimeMillis() - start;
System.out.println("all: " + result + " inner: " + inner);
return result;
}
public long test() throws Exception {
long innerstart = System.currentTimeMillis();
Thread.sleep(0, 100);
// Thread.sleep(500);
System.out.println(System.currentTimeMillis() - innerstart);
return System.currentTimeMillis() - innerstart;
}
}
方法c是用ReentrantLock实现的,绝大多少情况下ReentrantLock比synchronized高效
juc(java.util.concurrent)中的核心类Aqs(AbstractQueuedSynchronizer)是一个基于队列的 并发包,默认线程在锁竞争(自旋)超过1000纳秒的时候会被park(挂起操作),从而减少cpu频繁的线程切换,可以尝试调整方法c中的sleep的时间参数。
测试方法,本机没有装jmeter,用apache ab做的测试,测试命令:
ab -n 100 -c 20 http://localhost:8080/bench/a/10