
总结:
1. 如果在单线程环境下,几个操作共享变量的方法存在数据依赖关系,那么多线程环境下它们必须是一组原子操作,且与任何修改共享变量的方法互斥。与读方法是否互斥需要看程序的设计,比如 CopyOnWrite 模式下,这些原子操作不会与读共享变量的动作互斥,可以提高读的效率,但缺点是不能保证读操作每次读到的都是最新的值。
2. 保证多线程任务的正确性,是基于各线程对共享变量访问的正确性来保证的。
3. 我们还必须保证,多线程环境下存在数据依赖或控制依赖的方法,操作结果对其它线程的可见性以及操作对其它线程来说的有序性。happen-before 原则便是基于这两点的。
4. 尤其要注意单线程情况下不存在数据依赖,但与其它线程的执行有关的动作。因为单线程下不存在数据依赖,编译器很可能会进行乱序执行。比如修改共享变量并唤醒其它线程,单线程下这两个动作是不存在数据依赖的,如何乱序执行都不会影响单线程下的执行结果。但多线程环境下,被唤醒的线程可能是需要依据共享变量的值工作的,这两个动作在多线程环境下实际是存在数据依赖的。
题目很简单,使用多线程求一亿个数的和。这篇文章主要是为了总结一下多线程编程的思路,保证多线程任务的正确性,是基于各线程对共享变量访问的安全性来保证的。
定义共享数据:
public class ArraySource {
//源数组
private int[] source;
//累加结果
private int result = 0;
//当前工作的线程数
private int threadNum;
ArraySource(int[] source, int threadNum) {
this.source = source;
this.threadNum = threadNum;
}
public int[] getSource() {
return this.source;
}
public int getResult() {
return this.result;
}
public void setResult(int result) {
this.result = result;
}
public int getThreadNum() {
return this.threadNum;
}
public void setThreadNum(int threadNum) {
this.threadNum = threadNum;
}
}
定义线程任务:
public class SumThread extends Thread {
private int begin;
private int end;
ArraySource source;
Object lock = new Object();
SumThread(int begin, int end, ArraySource source) {
this.begin = begin;
this.end = end;
this.source = source;
}
@Override
public void run() {
if (this.source == null || this.begin >= this.end) {
throw new NullPointerException("非法入参!");
}
int re = 0;
int[] sourceArray = this.source.getSource();
for (int i = begin; i <= end; i++) {
re += sourceArray[i];
}
synchronized (lock) {
source.setResult(source.getResult() + re);
source.setThreadNum(source.getThreadNum() - 1);
}
}
}
主函数:
public class Test {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
int re = test();
if (re != 100000000) {
System.out.println("第 " + i + " 发生了错误,结果为" + re);
break;
}
}
System.out.println("测试结束");
}
public static int test() throws InterruptedException {
int[] source = new int[100000000];
for (int i = 0; i < 100000000; i++) {
source[i] = 1;
}
long beginTime = System.currentTimeMillis();
int re = 0;
for (int i = 0; i < 100000000; i++) {
re += source[i];
}
System.out.println("单线程用时为 :" + (System.currentTimeMillis() - beginTime));
System.out.println("单线程结果为 :" + re);
ArraySource arraySource = new ArraySource(source, 4);
SumThread thread0 = new SumThread(0, 20000000, arraySource);
SumThread thread1 = new SumThread(20000001, 40000000, arraySource);
SumThread thread2 = new SumThread(40000001, 60000000, arraySource);
SumThread thread3 = new SumThread(60000001, 99999999, arraySource);
// SumThread thread4 = new SumThread(80000001, 99999999, arraySource);
beginTime = System.currentTimeMillis();
thread0.start();
thread1.start();
thread2.start();
thread3.start();
// thread4.start();
while (arraySource.getThreadNum() != 0) {
// Thread.sleep(500);
// System.out.println("还有 : " + arraySource.getThreadNum() + " 个线程在工作;当前和为: " + arraySource.getResult());
}
System.out.println("多线程用时为 :" + (System.currentTimeMillis() - beginTime));
System.out.println("多线程结果为 :" + arraySource.getResult());
return arraySource.getResult();
}
}
主函数中执行了一千次代码以验证程序的正确性,最终证实程序是可靠的。
记一个错误的写法:
public class ArraySource {
//源数组
private int[] source;
//累加结果
private int result = 0;
//当前工作的线程数
private int threadNum;
ArraySource(int[] source, int threadNum) {
this.source = source;
this.threadNum = threadNum;
}
public int[] getSource() {
return this.source;
}
public int getResult() { synchronized(this) { return this.result; }}public void setResult(int result) { synchronized(this) { this.result = result; }}public int getThreadNum() { synchronized(this) { return this.threadNum; }}public void setThreadNum(int threadNum) { synchronized(this) { this.threadNum = threadNum; }}
这种写法等同于:
@Override
public void run() {
if (this.source == null || this.begin >= this.end) {
throw new NullPointerException("非法入参!");
}
int re = 0;
int[] sourceArray = this.source.getSource();
for (int i = begin; i <= end; i++) {
re += sourceArray[i];
}
int totalRe=source.getResult();
source.setResult( totalRe + re);
int sharedThreadNum=source.getThreadNum();
source.setThreadNum( sharedThreadNum - 1);
}
先获取读操作的锁,释放后去获取写操作的锁,并没有保证操作的原子性。
来源:https://www.cnblogs.com/niuyourou/p/12417138.html