主线程给不同子线程传参分配不同任务

遇到一个场景,请求报文进来服务后要从许多渠道收集相关信息,一般情况是串行下一个渠道获取完再到另一个渠道获取。这样一来响应时间普遍偏慢,甚至偶尔出现超时的现象,于是就想用多线程同时从不同渠道获取信息。先上代码,再说遇到的坑

public class KdAplyVO {
    public String name;
    public String id;
    public String code;
    public int  errMsg = 0;
省略set,get
import com.sun.xml.internal.ws.util.StringUtils;

import java.util.ArrayList;
import java.util.List;

public class TestVolatile {

    public static void main(String[] args) {

        String code1 = "01";//客户01

        String code2 = "02";//客户02


        //模拟webService层
        Thread thread3 = new Thread() {
            @Override
            public void run() {
                new QuseryMain().query(code1);
            }
        };
        thread3.start();

        //模拟webService层层
        Thread thread4 = new Thread() {
            @Override
            public void run() {
                new QuseryMain().query(code2);
            }
        };
        thread4.start();


    }
}

class QuseryMain {
//    private static  ThreadLocal<KdAplyVO> threadLocal = new ThreadLocal<>();  不能用threadlocal,子线程不同步

    public KdAplyVO query(String code) {
        //需持久化数据
        KdAplyVO kdAplyVO = new KdAplyVO();
        kdAplyVO.setCode(code);

        //模拟数据库获取任务
        List<String> taskList = new ArrayList<String>();
        taskList.add("setName");
        taskList.add("setId");
        taskList.add("setId1");
        taskList.add("setId2");
        taskList.add("setId3");
        taskList.add("setId4");
        taskList.add("setId5");
        taskList.add("setId6");



        List<Thread> threadList = new ArrayList<Thread>();

//        threadLocal.set(kdAplyVO);不能用threadlocal,子线程不同步

            for (String task : taskList) {
                QueryBS queryBS = new QueryBS(kdAplyVO, task);
                Thread thread = new Thread(queryBS);
                thread.start();
                threadList.add(thread);
            }

        //有超时风险,总的超时时间为30秒,每个线程最多等25秒,实际应用注意打日志用于判断哪个线程超时
        for (Thread thread : threadList) {
            try {
                thread.join(25000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }



        System.out.println("主1kdAplyVO = " + kdAplyVO.getName() + "," + kdAplyVO.getId());
        //本方法中的KdAplyVO并没有被volatile修饰,不休眠或者不打印的话不会停止循环,意味着变量没有同步到本线程中(原因可能为线程一直循环,虚拟机没空进行同步)
        // ,因此需要等线程join后才能保证同步?(测试没有join时会无线循环,添加join后则不会)网上查阅资料并没有明确说join后可以同步,保证同步的话可以考虑使用回调函数或future;
        while (true) {
            if (null != kdAplyVO.getName() && null != kdAplyVO.getId()) {
//                System.out.println("主1kdAplyVO = " + kdAplyVO.getCode() + "," + kdAplyVO.getName() + "," + kdAplyVO.getId());
                break;
            }

//            try {
//                Thread.sleep(1000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            System.out.println("主kdAplyVO = " +kdAplyVO.getName()+","+kdAplyVO.getId());
        }



        //此处可进行持久化
        System.out.println("主2kdAplyVO = " + kdAplyVO.getCode() + "," + kdAplyVO.getName() + "," + kdAplyVO.getId()+ "," + kdAplyVO.getErrMsg());
        return kdAplyVO;
    }

}


class QueryBS implements Runnable {
    //volatile 保证线程数据从内存中获取,因此即使QueryBS不是同一个对象,kdAplyVO是同一个对象应该也是能够同步的
    public  volatile KdAplyVO kdAplyVO;
    public String param;

    public QueryBS(KdAplyVO kdAplyVO, String param) {
        this.kdAplyVO = kdAplyVO;
        this.param = param;
    }

    @Override
    public void run() {
        if (this.param.equals("setName")) {
            if ("01".equals(kdAplyVO.code)) {
                //模拟从数据源获取数据花费的时间
//                System.out.println("code1休眠前");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
//                System.out.println("code1休眠后");
                this.kdAplyVO.setName("aliali");
                this.kdAplyVO.setErrMsg(this.kdAplyVO.getErrMsg()+1);
            } else {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.kdAplyVO.setName("bilibii");
                this.kdAplyVO.setErrMsg(this.kdAplyVO.getErrMsg()+1);
            }

        } else if (this.param.equals("setId")) {

            if ("01".equals(kdAplyVO.code)) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.kdAplyVO.setId("111111");
                this.kdAplyVO.setErrMsg(this.kdAplyVO.getErrMsg()+1);
            } else {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.kdAplyVO.setId("222222");
                this.kdAplyVO.setErrMsg(this.kdAplyVO.getErrMsg()+1);
            }
        }else{
            //KdAplyVO不被volatile修饰时会陷入死循环,修饰时则正常同步,说明即使QueryBS不是同一个对象,但是里面KdAplyVO被volatile修饰也会同步,因为它们都是操作主内存的数据
            while(this.kdAplyVO.getErrMsg() == 0){}
            this.kdAplyVO.setErrMsg(this.kdAplyVO.getErrMsg()+1);
        }
//        System.out.println("后kdAplyVO = " + kdAplyVO.getCode() + "," + kdAplyVO.getName() + "," + kdAplyVO.getId());
    }

    public KdAplyVO getKdAplyVO() {
        return kdAplyVO;
    }

    public void setKdAplyVO(KdAplyVO kdAplyVO) {
        this.kdAplyVO = kdAplyVO;
    }

    public String getParam() {
        return param;
    }

    public void setParam(String param) {
        this.param = param;
    }
}

首先第一个问题时如何给每个线程分配任务。
一种方法是每个任务单独写一个线程类,在主线程调用时直接判断调用,但是当任务量多时特别繁琐,而且也不容易对存量代码进行改造。
第二种方法就是将所有任务置入一个类统一管理中,如QueryBS类中的run方法,由主线程往子线程中传入参数,然后子线程依据参数来判断。
一开始的方案为共用一个QueryBS对象,主线程参数传入的参数绑定到threadlocal上,然后判断threadlocal上的参数进行判断进行哪个任务。但是并没有找到合适的方案。
网上有类似方案如参数传给QueryBS,再与threadlocal绑定,但我觉得不靠谱,如果是同一个QueryBS对象在绑定之前,第二个请求进来将QueryBS里参数改变就将引起混乱。如果再创建一个QueryBS对象,那就无需与threadlocal绑定了。
还有一种方案是从主线程的InheritableThreadLocal获取,但是一样面临上一种方案的问题,在子线程获取之前有可能下一个线程就进来改变了。
因此最后选择创建多个QueryBS对象的方式来为每个线程分配任务。


第二个问题就是数据同步问题,创建了一个持久化对象kdAplyVO,不能是主线程的成员变量,不能用volatile修饰。可以传入QueryBS对象,在线程对象QueryBS中可以用volatile修饰。
那么就又有两个问题,一个是子线程如何与主线程同步,测试发现由于方法中的KdAplyVO并没有被volatile修饰,不休眠或者不打印的话不会停止循环,意味着变量没有同步到本线程中(原因可能为线程一直循环,虚拟机没空进行同步)。
另一个问题是由于上一个任务分配问题,所以每个线程QueryBS对象是不一致的,那么里面的成员变量kdAplyVO是否还能同步

第一个问题一开始打算在主线程中threadlocal解决,但是仔细一想,各个线程中的threadlocal压根就是私有不同步的,此方案作废。然后,就考虑用线程join的方式,测试没有join时会无线循环,添加join后则不,但是网上查阅资料并没有明确说join后可以同步,希望哪位大佬可以指教下。另外保证同步的话可以考虑使用回调函数或future;或者可以使用CurrentHashMap来实现同步,晚点验证下
Java线程之异步回调(Callback)

第二个问题的话,由于kdAplyVO被volatile修饰,所以不管QueryBS对象是否一样,都是会去操作内存,从而实现同步。
以下为测试验证

            //KdAplyVO不被volatile修饰时会陷入死循环,修饰时则正常同步,说明即使QueryBS不是同一个对象,但是里面KdAplyVO被volatile修饰也会同步,因为它们都是操作主内存的数据
            while(this.kdAplyVO.getErrMsg() == 0){}
            this.kdAplyVO.setErrMsg(this.kdAplyVO.getErrMsg()+1);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值