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

来源:互联网 发布:php mvc框架有哪些 编辑:程序博客网 时间:2024/05/22 10:34

现在流行分布式计算,分布式计算就是先分开计算,然后统一汇总。比如这道题目: 这里写图片描述 。先别跑,小学题很简单的。
解释一下,左边那一砣是计算从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输出结果:3628800Accu结果:3628800Factorial结果: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输出结果:3628800Accu结果:55Factorial结果: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输出结果:3628800Accu结果:55Factorial结果: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最终计算结果3628855Accu结果:55Factorial结果:3628800
0 0
原创粉丝点击