Java多线程/并发13、保持线程间的数据独立: Collections.synchronizedMap应用

现在流行分布式计算,分布式计算就是先分开计算,然后统一汇总。比如这道题目: 这里写图片描述 。先别跑,小学题很简单的。
解释一下,左边那一砣是计算从1加到n的值(求和),右边是n乘到1的值(阶乘),再把两个值相加得到最终结果。假设求和运算需要5秒钟,阶乘运算需要7秒钟,相加的运算需要1秒,那么总耗时是13秒。而在分布式计算中,由两台机器同时进行计算,得到求和及阶乘的两个结果只需要7秒,再相加需要1秒,总耗时8秒。

下面,我们通过两个线程来模拟这个过程。

首先定义计算类,这三个类作为外部类,存在于Main()函数所在类的外面。代码很简单,看起来很长,是因为加了几组try..catch对。

/* 定义计算器,让计算由统一的类来管理 */
class Calculator {
    public static int result = 0;
     void getFactorial(int n) throws Exception {
        /* 模拟阶乘运算需要7秒 */
        try {
            Thread.sleep(7000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            /* 进行阶乘运算 */
            result = Factorial.GetResult(n);
        } catch (Exception e) {
            throw new Exception(e.getMessage());
        }
        System.out.println("线程" + Thread.currentThread().getName()
                + "输出结果:" + String.valueOf(Calculator.result));
    }

     void getAccu(int n) throws Exception {
        /* 模拟求和运算需要5秒 */
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            result = Accu.GetResult(n);/* 进行求和运算 */
        } catch (Exception e) {
            throw new Exception(e.getMessage());
        }
        System.out.println("线程" + Thread.currentThread().getName()
                + "输出结果:" + String.valueOf(Calculator.result));
    }
}

/* 定义阶乘处理类Factorial 这里是模拟实现,实际开发中需要用大数处理类BigInteger才行*/
class Factorial {
    public static int GetResult(int n) throws Exception {
        if (n < 0 || n > 10) {
            throw new Exception("error:玩玩而已,输入不能小于零,也不能大于10");
        }
        if (n <= 2) {
            return n;
        }
        return n * GetResult(n - 1);
    }
}

/* 定义求和处理类Accu */
class Accu {
    public static int GetResult(int n) throws Exception {
        if (n < 0 || n >= 1000) {
            throw new Exception("error:玩玩而已,输入不能小于零,也不能大于1000");
        }
        if (n <= 1) {
            return n;
        }
        return n + GetResult(n - 1);
    }
}

接下来,在Main()主线程中开始调用类来计算。

public class ExecuteDemo {
    public static void main(String[] args) {
        final Calculator calculator = new Calculator();
        new Thread(new Runnable() {
            public void run() {
                try {
                    calculator.getFactorial(10);
                } catch (Exception e) {
                    System.err.println(e.getMessage());
                }
                /*模拟很多线程并发运行时造成的竞争*/
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Factorial结果:" + String.valueOf(Calculator.result)); 

            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                try {
                    calculator.getAccu(10);
                } catch (Exception e) {
                    System.err.println(e.getMessage());
                }
                /*模拟很多线程并发运行时造成的竞争*/
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Accu结果:" + String.valueOf(Calculator.result));  
            }
        }).start();
    }
}

输出结果:

线程Thread-1输出结果:55
线程Thread-0输出结果:3628800
Accu结果:3628800
Factorial结果:3628800

线程调用方法运算的值是正确的,但是在取值却存在错误,Thread-1的计算结果被覆盖了。看来写出的这个计算类并不是线程安全的,问题就出在存放计算结果的变量static int result上。因为静态成员(static member)作为公共变量,就是放在共享内存区域的(方法区)。

在Calculator类中,我们用一个static int result来保存类中成员方法计算的结果,但多个线程并发调用方法时,都会抢占result向其写入数据,最终只会保留最后一个线程计算的值。我们需要让每个线程都保持各自的计算结果,自然想到了HashMap来保存。java提供了一个线程安全的HashMap包装: Collections.synchronizedMap。
我们来改动一下Calculator类,代码如下:

class Calculator {
    public static Map<Thread, Integer> DataContainer = Collections.synchronizedMap(new HashMap<Thread, Integer>());
     void getFactorial(int n) throws Exception {
        /* 模拟阶乘运算需要7秒 */
        try {
            Thread.sleep(7000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        int result=0;
        try {
            result = Factorial.GetResult(n);/* 进行阶乘运算 */
            DataContainer.put(Thread.currentThread(), result);
        } catch (Exception e) {
            throw new Exception(e.getMessage());
        }
        System.out.println("线程" + Thread.currentThread().getName()
                + "输出结果:" + String.valueOf(result));
    }

     void getAccu(int n) throws Exception {
        /* 模拟求和运算需要5秒 */
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        int result=0;
        try {
            result = Accu.GetResult(n);/* 进行求和运算 */
            DataContainer.put(Thread.currentThread(), result);
        } catch (Exception e) {
            throw new Exception(e.getMessage());
        }
        System.out.println("线程" + Thread.currentThread().getName()
                + "输出结果:" + String.valueOf(result));
    }
}

Main()函数的输出调整:

public static void main(String[] args) {
        final Calculator calculator = new Calculator();
        new Thread(new Runnable() {
            public void run() {
                try {
                    calculator.getFactorial(10);
                } catch (Exception e) {
                    System.err.println(e.getMessage());
                }
                /* 模拟耗时操作中多个线程运行时抢占造成的竞争 */
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Factorial结果:"
                        + String.valueOf(Calculator.DataContainer.get(Thread
                                .currentThread())));

            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                try {
                    calculator.getAccu(10);
                } catch (Exception e) {
                    System.err.println(e.getMessage());
                }
                /* 模拟耗时操作中多个线程运行时抢占造成的竞 */
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Accu结果:"
                        + String.valueOf(Calculator.DataContainer.get(Thread
                                .currentThread())));
            }
        }).start();
    }

两个线程互不影响,成功输出:

线程Thread-1输出结果:55
线程Thread-0输出结果:3628800
Accu结果:55
Factorial结果:3628800

最后,计算两个结果的和
在Main()函数两个线程调用的后面,加上下面这段,看看输出的结果:

Iterator<Map.Entry<Thread, Integer>> it=Calculator.DataContainer.entrySet().iterator();
int sum=0;
while(it.hasNext()){
    Map.Entry<Thread, Integer> entry = (Map.Entry<Thread, Integer>) it.next(); 
    Integer value = (Integer)entry.getValue(); 
    sum+=value;
}
System.out.println("最终计算结果"+String.valueOf(sum));

运行输出:

最终计算结果0
线程Thread-1输出结果:55
线程Thread-0输出结果:3628800
Accu结果:55
Factorial结果:3628800

我K,还是不对。
原来,进入Main()函数后,开启两个子线程计算后,主线程并没有停止,继续往下运行。由于两个子线程是耗时操作,而主线程如小李飞刀般快的速度往下运行,这时侯Calculator.DataContainer还是空的,结果当然为0。
我们应该阻塞主线程,直到Calculator.DataContainer有两个计算结果值后,才允许计算最后的相加结果。
Iterator it=Calculator.DataContainer.entrySet().iterator(); 这句的前面加上:

while(Calculator.DataContainer.size()<2){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

还有一种方法,调用两个线程的join()方法来实现调用线程(主线程)阻塞,等两个线程运行完毕再继续执行主线程,不过这要求两个线程不能用匿名方式实现,这里就不改程序了。具体join用法参考《Thread.Join()让调用线程等待子线程
运行输出:

线程Thread-1输出结果:55
线程Thread-0输出结果:3628800
最终计算结果3628855
Accu结果:55
Factorial结果:3628800
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值