现在流行分布式计算,分布式计算就是先分开计算,然后统一汇总。比如这道题目: 。先别跑,小学题很简单的。
解释一下,左边那一砣是计算从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