• 多线程求和,记一个 synchronized 的错误使用方式


      

      总结:

      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);
        }

      先获取读操作的锁,释放后去获取写操作的锁,并没有保证操作的原子性。

  • 相关阅读:
    jQuery中的一些操作
    laravel使用消息队列
    Laravel的开发环境Homestead的搭建与配置
    python爬虫学习
    配置文件
    sql根据时间差查询数据
    Oracle根据连接字符串获取库下的表列表、获取表结构
    Sql根据连接字符串获取库下的表列表、获取表结构
    判断网络连接
    线程锁,解决多线程并发问题
  • 原文地址:https://www.cnblogs.com/niuyourou/p/12417138.html
Copyright © 2020-2023  润新知