Java性能优化实践:异步多线程+同步执行(上)

简介: Java性能优化实践:异步多线程+同步执行(上)

当项目中某个业务需要调用另外N个服务接口,然后根据返回的结果做筛选再返回给前端。当然最简单的做法就是N个接口串行调用,但是如果每个接口调用的时间都在1秒以上那么N个接口调用完毕就需要耗费N秒,这在项目中是不可接受的,因此这个时候就需要用多线程处理。


而主线程要等待所有子线程任务执行完毕后再往下同步执行的实现方式,主要有以下几种方法:


  • 1.使用synchronized获得同步锁
  • 2.CountDownLatch
  • 3.Thread Join
  • 4.CyclicBarrier
  • 5.Future
  • 6.CompletableFuture

方法一: 使用synchronized获得同步锁


这是最基础的方式,就是放置一个公用的static变量,假如有10个线程,每个线程处理完上去累加下结果,然后后面用一个死循环(或类似线程阻塞的方法),去数这个结果,达到10个,说明大家都执行完了,就可以执行后续的事情了,这个想法虽然土鳖,但是基本上跟语言无关,几乎所有主流编程语言都支持。

public class ThreadLockTest {
    public static Integer flag = 0;//公用变量
    public static void main(String[] args) throws Exception {
        ThreadLockTest testObj = new ThreadLockTest();
        final int threadNum = 10;
        for (int i = 0; i < threadNum; i++) {
            new Thread(new MyRunable(i, testObj)).start();
        }
        while (true) {
            if (testObj.flag >= threadNum) {
                System.out.println("-----------\n所有thread执行完成!");
                break;
            }
            Thread.sleep(10);
        }
    }
    static class MyRunable implements Runnable {
        int _i = 0;
        ThreadLockTest _test;
        public MyRunable(int i, ThreadLockTest test) {
            this._i = i;
            this._test = test;
        }
        @Override
        public void run() {
            try {
                Thread.sleep((long) (Math.random() * 10));
                System.out.println("thread " + _i + " done");
                //利用synchronized获得同步锁
                synchronized (_test) {
                    _test.flag += 1;
                }
                System.out.println("thread " + _i + " => " + _test.flag);//测试用
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}


方法二: CountDownLatch


使用CountDownLatch 这个类是在JDK1.5就已经提供了,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。


其实,CountDownLatch实现方式,跟上面的方法一是类似的。它是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,接下来等待的线程就可以恢复工作了。

下面咱们简单看下源码:


countDownLatch类中只提供了一个构造器:

//参数count为计数值
public CountDownLatch(int count) {  };


而类中有三个方法是最重要的:

//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public void await() throws InterruptedException { };   
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  
//将count值减1
public void countDown() { };


以下例子就是一个很经典的CountDownLatch的用法

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownLatchTest {
    public static void countDownLatchTest() {
        long time = System.currentTimeMillis();
        final CountDownLatch countDownLatch = new CountDownLatch(5);
        ExecutorService es = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            final int num = i;
            es.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(num * 1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    /**
                     * 使用CountDownLatch时要注意异常情况,一旦没处理好导致countDownLatch.countDown()没执行会引起线程阻塞,导致CPU居高不下**/
                    /*if (num == 3) {
                        System.out.println(Integer.parseInt("1.233"));
                    }*/
                    countDownLatch.countDown();
                    System.out.println(Thread.currentThread().getName() + "运行结束  运行时间为:" + num
                            + "秒  countDownLatch=" + countDownLatch.getCount());
                }
            });
        }
        es.shutdown();
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("总耗时===" + (System.currentTimeMillis() - time));
    }
    public static void main(String[] args) {
        countDownLatchTest();
    }
}


可以看到最终耗时跟线程中最耗时的线程有关,但是使用CountDownLatch有一定风险,如果运行中没有捕获相关异常很容易导致CPU居高不下从而导致整个项目无法运行(想测试的同学可以把countDownLatchTest中的注解打开)。


那么遇到这种问题如何处理,当然把整个代码try catch是一种解决方式。另外一种比较优雅的解决方式是使用countDownLatch.await(5, TimeUnit.SECONDS) 代替countDownLatch.await(),方法中的那2个参数分别是超时时间和超时单位,如果线程在规定的时间内没有处理完成则主线程被自动唤醒继续执行下一步操作。


以上方法可以实现对应功能但是有以下缺点:


  • (1)容易出错,如果没有捕获异常或没设置超时时间很容易造成服务器死机;
  • (2)没有返回值,当然可以用静态变量存储(不推荐);

方法三: Thread Join


join()方法的作用,让父线程等待子线程结束之后才能继续运行。


官方的解释是,当我们调用某个线程的这个方法时,这个方法会挂起调用线程,直到被调用线程结束执行,调用线程才会继续执行。


通过查看源码,会看到Join方法实现是通过wait方法,当main线程调用thread.join()时候,main线程会获得线程对象thread的锁(wait 意味着拿到该对象的锁),调用该对象的wait(等待时间),直到该对象唤醒main线程 ,比如退出后。


以下例子也是很经典的用法:

public class JoinTest {
    public static void main(String[] args) {
        joinTest();
    }
    public static void joinTest() {
        long time = System.currentTimeMillis();
        int threadNum = 5;
        Thread[] threads = new Thread[threadNum];
        for (int i = 1; i <= threadNum; i++) {
            final int num = i;
            threads[i - 1] = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(num * 1000);
                        System.out.println(Thread.currentThread().getName() + "耗时:" + num + "秒");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            threads[i - 1].start();
        }
        for (int i = 0; i < threads.length; i++) {
            try {
                threads[i].join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("总耗时===" + (System.currentTimeMillis() - time));
    }
}


这个方法的效果跟上面使用CountDownLatch方式差不多,优缺点也一样,所以就不在过多探讨了。


相关文章
|
18天前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
Java 数据库 Spring
54 0
|
1月前
|
算法 Java
Java多线程编程:实现线程间数据共享机制
以上就是Java中几种主要处理多线程序列化资源以及协调各自独立运行但需相互配合以完成任务threads 的技术手段与策略。正确应用上述技术将大大增强你程序稳定性与效率同时也降低bug出现率因此深刻理解每项技术背后理论至关重要.
82 16
|
2月前
|
缓存 并行计算 安全
关于Java多线程详解
本文深入讲解Java多线程编程,涵盖基础概念、线程创建与管理、同步机制、并发工具类、线程池、线程安全集合、实战案例及常见问题解决方案,助你掌握高性能并发编程技巧,应对多线程开发中的挑战。
|
2月前
|
数据采集 存储 前端开发
Java爬虫性能优化:多线程抓取JSP动态数据实践
Java爬虫性能优化:多线程抓取JSP动态数据实践
|
3月前
|
数据采集 监控 调度
干货分享“用 多线程 爬取数据”:单线程 + 协程的效率反超 3 倍,这才是 Python 异步的正确打开方式
在 Python 爬虫中,多线程因 GIL 和切换开销效率低下,而协程通过用户态调度实现高并发,大幅提升爬取效率。本文详解协程原理、实战对比多线程性能,并提供最佳实践,助你掌握异步爬虫核心技术。
|
3月前
|
存储 监控 算法
企业上网监控场景下布隆过滤器的 Java 算法构建及其性能优化研究
布隆过滤器是一种高效的数据结构,广泛应用于企业上网监控系统中,用于快速判断员工访问的网址是否为违规站点。相比传统哈希表,它具有更低的内存占用和更快的查询速度,支持实时拦截、动态更新和资源压缩,有效提升系统性能并降低成本。
73 0
|
3月前
|
安全 算法 Java
Java 多线程:线程安全与同步控制的深度解析
本文介绍了 Java 多线程开发的关键技术,涵盖线程的创建与启动、线程安全问题及其解决方案,包括 synchronized 关键字、原子类和线程间通信机制。通过示例代码讲解了多线程编程中的常见问题与优化方法,帮助开发者提升程序性能与稳定性。
143 0
|
4月前
|
Java 数据挖掘 调度
Java 多线程创建零基础入门新手指南:从零开始全面学习多线程创建方法
本文从零基础角度出发,深入浅出地讲解Java多线程的创建方式。内容涵盖继承`Thread`类、实现`Runnable`接口、使用`Callable`和`Future`接口以及线程池的创建与管理等核心知识点。通过代码示例与应用场景分析,帮助读者理解每种方式的特点及适用场景,理论结合实践,轻松掌握Java多线程编程 essentials。
249 5
|
8月前
|
Python
python3多线程中使用线程睡眠
本文详细介绍了Python3多线程编程中使用线程睡眠的基本方法和应用场景。通过 `time.sleep()`函数,可以使线程暂停执行一段指定的时间,从而控制线程的执行节奏。通过实际示例演示了如何在多线程中使用线程睡眠来实现计数器和下载器功能。希望本文能帮助您更好地理解和应用Python多线程编程,提高程序的并发能力和执行效率。
258 20