1.场景:
测试同学提出线上环境某个接口响应速度过慢,分析代码看到该接口调用了4个接口,之后再进行计算,响应时间为300ms。
优化方案: 采用多线程获取4个接口的执行结果,将响应时间将为70ms
2.多线程引发的问题:
进行多线程处理时,需根据传递的参数,执行相应的方法,并返回结果,由于参数其中有一个共用的,可能会导致 同一时间A线程已经更改了对象,B线程这时也更改了对象,从而引发了线程安全问题
3.模拟测试:
package cn.sys.Test.thread.callable;
import lombok.Getter;
import lombok.Setter;
import java.util.concurrent.*;
class Task implements Callable<String> {
private ThreadPoolExecutorTest test;
private String name;
private ThreadPoolExecutorTest.Student student;
public Task(ThreadPoolExecutorTest test, ThreadPoolExecutorTest.Student student, String name) {
this.test = test;
this.student = student;
this.name = name;
}
@Override
public String call() throws Exception {
if ("张三".equals(name)) {
return test.show2(student);
}
if ("李四".equals(name)) {
return test.show3(student);
}
if ("王二".equals(name)) {
return test.show4(student);
}
return "返回值" + student;
}
}
public class ThreadPoolExecutorTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService tpe = Executors.newFixedThreadPool(4);
ThreadPoolExecutorTest test = new ThreadPoolExecutorTest();
//注意:这里会有线程安全问题
Student student = new Student();
student.setName("张三");
Task task = new Task(test, student, "张三");
Task task2 = new Task(test, student, "李四");
Task task3 = new Task(test, student, "王二");
Future<String> result = tpe.submit(task);
Future<String> result2 = tpe.submit(task2);
Future<String> result3 = tpe.submit(task3);
System.out.println("张三?: " + result.get());
System.out.println("李四?: " + result2.get());
System.out.println("王二?: " + result3.get());
tpe.shutdown();
System.out.println("线程结束");
}
public String show2(Student student){
student.setName("张三");
return student.getName();
}
public String show3(Student student){
student.setName("李四");
return student.getName();
}
public String show4(Student student){
student.setName("王二");
return student.getName();
}
/**
* @description: 学生类
* @date: 2021/3/11 16:30
*/
@Getter
@Setter
static class Student{
private String name;
}
}
执行结果:
张三?: 张三
李四?: 李四
王二?: 李四 //这里应该是 王二 才对。 线程安全问题导致的
线程结束
通过执行结果:线程出现安全问题
4.解决方案:
1.创建多个Student对象这样每个线程操作的对象就是安全的。优势:如果线程不多,可提高执行速度 弊端:创建对象较多
2.用synchronized () 锁student对象,保证只有一个线程在对student做处理,优势:简单易用 弊端:影响执行速度,实际也是一个单线程运行
这两种方案根据实际情况,选择其一即可
package cn.sys.Test.thread.callable;
import lombok.Getter;
import lombok.Setter;
import java.util.concurrent.*;
class Task implements Callable<String> {
private ThreadPoolExecutorTest test;
private String name;
private ThreadPoolExecutorTest.Student student;
public Task(ThreadPoolExecutorTest test, ThreadPoolExecutorTest.Student student, String name) {
this.test = test;
this.student = student;
this.name = name;
}
@Override
public String call() throws Exception {
// 解决方案2:用synchronized () 锁student对象,保证只有一个线程在对student做处理,优势:简单易用 弊端:影响执行速度
synchronized (student){
if ("张三".equals(name)) {
return test.show2(student);
}
if ("李四".equals(name)) {
return test.show3(student);
}
if ("王二".equals(name)) {
return test.show4(student);
}
}
return "返回值" + student;
}
}
public class ThreadPoolExecutorTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService tpe = Executors.newFixedThreadPool(4);
ThreadPoolExecutorTest test = new ThreadPoolExecutorTest();
//这里会产生线程安全。
//原因:对象的引用传递。该对象被多个线程进行更改,同一时间A线程已经成功赋值,由于线程是并行的,此时B线程也更改了此对象,就导致A出现线程安全问题
//解决方案:1.创建多个Student对象这样每个线程操作的就是独立的对应。优势:如果线程不多,可提高执行速度 弊端:频繁创建对象
Student student = new Student();
student.setName("张三");
Student student2 = new Student();
student.setName("李四");
Student student3 = new Student();
student.setName("王二");
Task task = new Task(test, student, "张三");
Task task2 = new Task(test, student2, "李四");
Task task3 = new Task(test, student3, "王二");
Future<String> result = tpe.submit(task);
Future<String> result2 = tpe.submit(task2);
Future<String> result3 = tpe.submit(task3);
System.out.println("张三?: " + result.get());
System.out.println("李四?: " + result2.get());
System.out.println("王二?: " + result3.get());
tpe.shutdown();
System.out.println("线程结束");
}
public String show2(Student student){
student.setName("张三");
return student.getName();
}
public String show3(Student student){
student.setName("李四");
return student.getName();
}
public String show4(Student student){
student.setName("王二");
return student.getName();
}
/**
* @description: 学生类
* @date: 2021/3/11 16:30
*/
@Getter
@Setter
static class Student{
private String name;
}
}
更改代码之后 多次执行返回的结果都是一样的,符合我们预期:
张三?: 张三
李四?: 李四
王二?: 王二
线程结束
在追踪线程安全问题时,刚开始没有想到是多线程操作的是同一对象导致的,直到头发掉光才发觉,特此记录一下