记录多线程使用引发的线程安全问题 -- Callable<T> 返回值场景

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;
    }

}

更改代码之后 多次执行返回的结果都是一样的,符合我们预期:

张三?: 张三
李四?: 李四
王二?: 王二
线程结束

在追踪线程安全问题时,刚开始没有想到是多线程操作的是同一对象导致的,直到头发掉光才发觉,特此记录一下

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
package step2; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; // ---------------------Begin------------------------ //tips: 输出语句为:Thread.currentThread().getName()+"的call()方法在运行" //定义一个实现Callable接口的实现类,类名必须命名为MyThread_callable class MyThread_callable<T> implements Callable<T> { public T call() throws Exception { return (T) (Thread.currentThread().getName() + "的call()方法在运行"); }} // ---------------------End------------------------ public class CallableExample { public static void main(String[] args) throws InterruptedException, ExecutionException { // 创建Callable接口的实现类对象 MyThread_callable myThread3 = new MyThread_callable(); // 使用FutureTask封装Callable接口 FutureTask<Object> ft1 = new FutureTask<>(myThread3); // 使用Thread(Runnable target ,String name)构造方法创建线程对象 Thread thread1 = new Thread(ft1, "thread1"); // 创建并启动另一个线程thread2 FutureTask<Object> ft2 = new FutureTask<>(myThread3); Thread thread2 = new Thread(ft2, "thread2"); // ---------------------Begin------------------------ // 调用线程对象的start()方法启动线程 System.out.println(thread1.start()) ; System.out.println(thread2.start()) ; // 可以通过FutureTask对象的方法管理返回 Object result1 = ft1.get(); Object result2 = ft2.get(); System.out.println(result1); System.out.println(result2); // ---------------------End------------------------ } }
最新发布
06-11

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值