java并发编程实战之从因数分解看多线程(上)

java并发编程实战之从因数分解看多线程

这篇博文主要通过对多个大数的因数分解进行实践,进而引出初步的多线程的一些用法和思考。

1.因数分解

因数分解是对一个数字m,找出它所有的因数的过程,比如说数字6 因数分解之后为 2x3

2.为何选择因数分解

因为因数分解是一个很费事的计算过程,目前没有很好的算法可以提高效率,可以很好的用来模拟复杂计算处理过程,以便于我们明显的去观察在不同的情况下线程的状态

  1. 因数分解的算法代码如下

具体就是先从2开始除,看2能不能被整除,如果可以则继续除以2,如果不能,则除以3 以此类推,直到除数不在小于m

代码如下

public class factorsService {

    //建立一个因式分解服务
    public void Service(Long i) {

        List<Long> factors = factor(i);
    }

    private List<Long> factor(Long i) {
        //时间起点
        long begintime = System.currentTimeMillis();

        Long j = 2L;
        Long k = 0L;
        Long start = i;
        List<Long> factors = new ArrayList<Long>();
            while (j <= i) {
                if (i % j == 0) {
                    i = i / j;
                    factors.add(j);
                } else {
                    j++;
                }
                k++;
            }


        System.out.println("计算结束,循环了" + k + "次");
        System.out.print(start + "的因数分解为: ");
        Long var = 1L;
        for (int i1 = 0; i1 < factors.size(); i1++) {
            System.out.print(factors.get(i1));
            if (i1 < factors.size() - 1) {
                System.out.print("X");
            }

            var *= factors.get(i1);
        }
        System.out.println();
        System.out.println("验证:分解出来因数相乘结果为:" + var);
        long endtime = System.currentTimeMillis();
        System.out.println("耗时" + (endtime - begintime) + "毫秒");
        return factors;
    }
}

main方法如下

 public static void main(String[] args) {
        factorsService factorsService = new factorsService();

        List<Long> nums = Arrays.asList(1323999999999L, 1323999999999L); 
        Long begin=System.currentTimeMillis();
        for (Long num : nums) {
            factorsService.Service(num);
        }
        Long end=System.currentTimeMillis();

        System.out.println("2个任务计算完成,耗时:"+(end-begin)+"毫秒");
}

我们来看下输出, 这是对两个Long型的整数进行因数分解,此时没有使用多线程,串行执行

运行结果如下

计算结束,循环了374328530次
1323999999999的因数分解为: 3X3X3X131X374328527
验证:分解出来因数相乘结果为:1323999999999
耗时5374毫秒
计算结束,循环了374328530次
1323999999999的因数分解为: 3X3X3X131X374328527
验证:分解出来因数相乘结果为:1323999999999
耗时4698毫秒
2个任务计算完成,耗时:10073毫秒

我们可以看到,两个数的因数分解 总计花了10073秒

那么有什么更好的方法可以提升速度吗?答案是从算法来说,其实是没有 的,大数的因数分解很难做,因此奠定了非对称加密的基石(非对称加密的秘钥生成利用是利用到了这点特性,已知积很难算出因数,但是已知因数却很容易算出积) 好了,算法考虑优化不是我们现在这个博文的主题,接下来进入正题,我们使用多线程解决

3.什么是多线程,为什么要用多线程

通俗的来说线程是比进程更轻量级的调度单元,一个进程里面可以包含多个线程。
举个例子,我们常说的八核cpu 指定的就是1个cpu有8个物理处理单元,也就是最多可以同时跑8个物理线程。 (cpu还有超线程技术,可以用一个物理线程模拟多个线程)
既然cpu如此强大,但是如果我们的程序是单线程的,那么cpu的强大功能就浪费了,单线程用不上多核。
疑问1:我们用单线程做了两个因数分解,耗时10073毫秒,那么我们起两个线程,同时做这个因数分解的任务,那是不是只需要一半的时间?
疑问2:如果我有5个数要做因数分解,那我起五个线程,是不是也可以缩小5倍时间?

4.多线程跑因数分解的任务代码如下

public class factorsThread implements Runnable {
    private CountDownLatch latch;

    public factorsThread(CountDownLatch latch) {
        this.latch = latch; //初始化闭锁
    }


    factorsService factorsService;

    public void setFactorsService(factorsService factorsService) {
        this.factorsService = factorsService;
    }


    Long num = 0L;

    public void setNum(Long num) {
        
        this.num = num;
    }




    public void run() {
        factorsService.Service(num);
        latch.countDown();  //CountDownLatch在执行完run后减1
    }
}

CountDownLatch 闭锁,一开始维护一个初始变量值,执行countDown()之后变量值减1

引入这个CountDownLatch是为了在某一个点同步线程,让线程在同一个地方停下来,可以想象为一道门,然后多个线程都在这个门前停下来,好方便我们统计多线程的执行的时间。

CountDownLatch的用法就是,先在要使用CountDownLatch的线程类中声明一个CountDownLatch属性,并在run方法执行完之后调用 latch.countDown();将计数器减1:
例如

public class factorsThread implements Runnable {
    private CountDownLatch latch;

    public factorsThread(CountDownLatch latch) {
        this.latch = latch; //初始化闭锁
    }
    
....
 public void run() {
        factorsService.Service(num);
        latch.countDown();  //CountDownLatch在执行完run后减1
    }


然后在需要使用这个Thread类的地方,创建一个CountDownLatch的对象,并传入需要同步的线程数量,然后将CountDownLatch的象
传入到Thread类latch字段值里,然后启动这个线程

 //创建CountDownLatch对象,2表示最终要同步的是2个线程
        final CountDownLatch latch = new CountDownLatch(2);
        factorsThread factorsThread = new factorsThread(latch);
        factorsThread.setFactorsService(factorsService);
        
        System.out.println("多线程的方式启动计算:");
        Long beginT = System.currentTimeMillis();
        for (Long num : nums) {
            factorsThread.setNum(num);
            Thread thread = new Thread(factorsThread);//新建线程
            thread.start();
        }
        
        ...

最后在需要等待所有线程完成任务的地方

 try {
            //多线程运行结束前一直等待
            latch.await(); //闭锁的作用,阻塞等待 直到countDown为0
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

具体如下:
比如说,我们并发了2个线程,要怎么统计这两个线程执行结束后花了多少时间?可以在线程启动之前,读取到当前系统时间,然后在 latch.await()之后,所有多线程执行完毕之后,再读取一次时间,时间差即是多线程执行期间的总时间。

 public static void main(String[] args) {
        factorsService factorsService = new factorsService();

        List<Long> nums = Arrays.asList(1323999999999L, 1323999999999L);
        //开始时间
        Long begin=System.currentTimeMillis();
        
        //创建CountDownLatch对象,2表示最终要同步的是2个线程
        final CountDownLatch latch = new CountDownLatch(2);
        factorsThread factorsThread = new factorsThread(latch);
        factorsThread.setFactorsService(factorsService);
        
        System.out.println("多线程的方式启动计算:");
        Long beginT = System.currentTimeMillis();
        for (Long num : nums) {
            factorsThread.setNum(num);
            Thread thread = new Thread(factorsThread);//新建线程
            thread.start();
        }

        try {
            //多线程运行结束前一直等待
            latch.await(); //闭锁的作用,阻塞等待 直到countDown为0
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

//结束时间
        Long endT = System.currentTimeMillis();
        System.out.println("多线程任务计算完成!耗时" + (endT - beginT) + "毫秒");


    }

输出结果:

多线程的方式启动计算:
计算结束,循环了374328530次
1323999999999的因数分解为: 3X3X3X131X374328527
验证:分解出来因数相乘结果为:1323999999999
耗时5721毫秒
计算结束,循环了374328530次
1323999999999的因数分解为: 3X3X3X131X374328527
验证:分解出来因数相乘结果为:1323999999999
耗时5750毫秒
多线程任务计算完成!耗时5751毫秒

结论:两个线程并发运行的时候,时间缩小了接近一半,解决疑问1

疑问二,当我们5个线程的时候并发会怎样?
代码如下

main代码

  public static void main(String[] args) {
        factorsService factorsService = new factorsService();

        List<Long> nums = Arrays.asList(1323999999999L, 1323999999999L,1323999999999L, 1323999999999L, 1323999999999L); //51323999999999L, 51323999999999L, 51323999999998L);
        Long begin=System.currentTimeMillis();
        for (Long num : nums) {
            factorsService.Service(num);
        }
        Long end=System.currentTimeMillis();

        System.out.println("5个任务计算完成,耗时:"+(end-begin)+"毫秒");

        final CountDownLatch latch = new CountDownLatch(2);
        factorsThread factorsThread = new factorsThread(latch);
        factorsThread.setFactorsService(factorsService);

        System.out.println("多线程的方式启动计算:");
        Long beginT = System.currentTimeMillis();
        for (Long num : nums) {
            factorsThread.setNum(num);
            Thread thread = new Thread(factorsThread);
            thread.start();
        }

        try {
            //多线程运行结束前一直等待
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Long endT = System.currentTimeMillis();
        System.out.println("多线程任务计算完成!耗时" + (endT - beginT) + "毫秒");

    }

service代码

public class factorsService {

    //建立一个因式分解服务
    public void Service(Long i) {

        List<Long> factors = factor(i);
    }

    private List<Long> factor(Long i) {
        //时间起点
        long begintime = System.currentTimeMillis();

        Long j = 2L;
        Long k = 0L;
        Long start = i;
        List<Long> factors = new ArrayList<Long>();
        while (j <= i) {
            if (i % j == 0) {
                i = i / j;
                factors.add(j);
            } else {
                j++;
            }
            k++;
        }

        synchronized (this) { //主要是新增了这块的同步,以便于打印控制台消息的时候顺序不会乱  
        //因为线程是交替执行的,这部分不同步的话打印会乱序。
        //这部分代码的同步不会影响什么时间,因为主要的时间消耗不在这部分逻辑上
            System.out.print(start + "的因数分解为: ");
            Long var = 1L;
            for (int i1 = 0; i1 < factors.size(); i1++) {
                System.out.print(factors.get(i1));
                if (i1 < factors.size() - 1) {
                    System.out.print("X");
                }

                var *= factors.get(i1);
            }
         
            long endtime = System.currentTimeMillis();
            System.out.println("耗时" + (endtime - begintime) + "毫秒");
        }
        return factors;
    }
}

输出结果

1323999999999的因数分解为: 3X3X3X131X374328527 耗时7032毫秒
1323999999999的因数分解为: 3X3X3X131X374328527 耗时6666毫秒
1323999999999的因数分解为: 3X3X3X131X374328527 耗时5614毫秒
1323999999999的因数分解为: 3X3X3X131X374328527 耗时4303毫秒
1323999999999的因数分解为: 3X3X3X131X374328527 耗时4295毫秒
5个任务计算完成,耗时:27910毫秒
多线程的方式启动计算:
1323999999999的因数分解为: 3X3X3X131X374328527 耗时9757毫秒
1323999999999的因数分解为: 3X3X3X131X374328527 耗时9777毫秒
1323999999999的因数分解为: 3X3X3X131X374328527 耗时9798毫秒
1323999999999的因数分解为: 3X3X3X131X374328527 耗时9815毫秒
1323999999999的因数分解为: 3X3X3X131X374328527 耗时9807毫秒
多线程任务计算完成!耗时9779毫秒

疑问2:5个线程是否可以缩短五倍时间,答案是不能
27910/9779 大概就是2.7倍的差距,并没有5倍

因此提升线程并不是线性的提升效率,反而随着线程数量的增加,cpu会花费大量的时间用于处理各个线程之间的切换上

切换时候需要保存各个线程的状态,又要恢复,而且各个线程之间彼此还会竞争cpu

合理的线程数目约为N 或者N+1 N为cpu核心数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值