1.synchronized关键字的使用
2.场景1
在一个Service中有两个方法,method1,method2,实现给两个方法都加锁,但其互不影响,也就是method1方法不能两个线程同时执行,method2方法也不能两个线程同时执行,但mehtod1在执行时method2方法可以执行,method2方法在执行时method1方法可以执行
1.若在方法上加synchronized关键字则不能达到预期效果
synchronized关键字加载方法上相当于对该Service实例对象加锁,而一般来说Service是单例的,此时mehtod1方法和method2方法会互相影响,即method1方法在执行时,不仅不能执行method1方法,method2方法也不能执行。
2.定义一个成员对象Object,使用Object作为锁
可以定义一个成员对象Object,在mehtod2方法中通过方法内使用synchronized代码块方式,指定锁为Object对象,即可达到预期效果,method1可以保持不变,即在方法上使用synchronize关键字。此时相当于method1使用的锁是该Service的实例对象,而method2方法使用的锁是Object对象,两者互不影响。
//为了演示方便,使用Controller层代码演示,效果同Service层代码
private final Object object = new Object();
@RequestMapping(value = "/method1", method = RequestMethod.GET)
public synchronized String method1(){
System.out.println("method1 wait 10s start...");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("method1 wait 10s end...");
return "method1 success";
}
@RequestMapping(value = "/method2", method = RequestMethod.GET)
public String method2() {
synchronized (object){
System.out.println("method1 wait 5s start...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("method1 wait 5s end...");
return "method2 success";
}
}
3.可以定义两个Object对象,每个方法使用一个作为锁
这里和第二种方式本质是一样的,只是把mehtod1使用的锁从Service实例对象换为了Object对象,这样同样可以达到预期效果
4.使用Lock对象代替synchronized关键字
定义两个Lock对象,这里使用可重入锁ReentrantLock实现,在每个方法体内使用lock和unlock方法实现加锁,释放锁,原理与第三种方法相同,可以达到预期效果。
3.场景2
Service中有两个方法method3,method4,method3方法执行比较耗时,前端调用此方法会出现超时,且影响用户体验,因此需要定义method4方法,前端请求method4方法,该方法直接返回结果,新起线程执行method3方法,但此时有一个问题,因为前端调用method4方法直接返回了结果,因此无法判断method3方法是否执行完成,因为method3方法不能多个线程同时调用,也就是只有其执行完成才能再次调用,否则会出现业务逻辑出错,因此在调用method4方法时判断method3方法是否正在被执行,若在执行则抛出错误信息。
1.使用Lock对象的tryLock方法 + ThreadPoolTaskExecute的submit方法实现
定义一个Lock对象,在method3开始执行时先尝试获取锁,若获取到锁,则执行业务逻辑,此处使用线程休眠10秒代替耗时的业务逻辑,若没有获取到锁,则直接返回。method4方法在提交完任务后,等待1秒或更短的时间去拿任务执行的结果,若拿到,说明method3方法没有获取到锁,因为若拿到了锁,其业务逻辑需要耗时10秒,在提交任务1秒或更短的时间内不可能拿到执行结果,若没有拿到,说明method3方法拿到了锁,正在执行业务逻辑。因此如果拿到了执行结果,则直接返回页面method3方法正在执行请稍后再操作类似信息,若没有拿到执行结果,则返回页面method3方法开始执行,请稍后类似信息。
下面例子中method4若拿到了任务的执行结果则返回执行结果,若没有拿到则返回null
//为了演示方便,使用Controller层代码演示,效果同Service层代码
private final Lock lock = new ReentrantLock();
@Resource(name = "myAsync")
private ThreadPoolTaskExecutor executor;
@RequestMapping(value = "/method3", method = RequestMethod.GET)
public String method3(){
boolean b = lock.tryLock();
if(b){
try {
System.out.println("method3 wait 10s start...");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("method3 wait 10s end...");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
return "method3 success";
}else {
return "method3 no lock...";
}
}
@RequestMapping(value = "/method4", method = RequestMethod.GET)
public String method4(){
Future<String> submit = executor.submit(() -> method3());
String s = null;
try {
s = submit.get(1, TimeUnit.SECONDS);
} catch (Exception e){
}
System.out.println(s);
return s;
}
4.谷歌浏览器测试时发现的问题
当使用谷歌浏览器访问同一个Controller的某一个方法时,若在一个页签去访问,则controller中方法可以并行进行,符合预期。
若使用两个页签去访问同一个方法,则会出现该方法串行进行,通过测试发现其不是真正的串行,而是第二次访问时会等待好久才能访问到。
因此此问题定位到是浏览器的问题。