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方式差不多,优缺点也一样,所以就不在过多探讨了。


相关文章
|
9天前
|
存储 Java 数据库连接
java多线程之线程通信
java多线程之线程通信
|
9天前
|
Java 数据挖掘
java实践
【4月更文挑战第9天】java实践
12 1
|
1天前
|
负载均衡 Java 开发者
细解微服务架构实践:如何使用Spring Cloud进行Java微服务治理
【4月更文挑战第17天】Spring Cloud是Java微服务治理的首选框架,整合了Eureka(服务发现)、Ribbon(客户端负载均衡)、Hystrix(熔断器)、Zuul(API网关)和Config Server(配置中心)。通过Eureka实现服务注册与发现,Ribbon提供负载均衡,Hystrix实现熔断保护,Zuul作为API网关,Config Server集中管理配置。理解并运用Spring Cloud进行微服务治理是现代Java开发者的关键技能。
|
1天前
|
网络协议 Java API
深度剖析:Java网络编程中的TCP/IP与HTTP协议实践
【4月更文挑战第17天】Java网络编程重在TCP/IP和HTTP协议的应用。TCP提供可靠数据传输,通过Socket和ServerSocket实现;HTTP用于Web服务,常借助HttpURLConnection或Apache HttpClient。两者结合,构成网络服务基础。Java有多种高级API和框架(如Netty、Spring Boot)简化开发,助力高效、高并发的网络通信。
|
1天前
|
存储 缓存 安全
Java并发基础之互斥同步、非阻塞同步、指令重排与volatile
在Java中,多线程编程常常涉及到共享数据的访问,这时候就需要考虑线程安全问题。Java提供了多种机制来实现线程安全,其中包括互斥同步(Mutex Synchronization)、非阻塞同步(Non-blocking Synchronization)、以及volatile关键字等。 互斥同步(Mutex Synchronization) 互斥同步是一种基本的同步手段,它要求在任何时刻,只有一个线程可以执行某个方法或某个代码块,其他线程必须等待。Java中的synchronized关键字就是实现互斥同步的常用手段。当一个线程进入一个synchronized方法或代码块时,它需要先获得锁,如果
17 0
|
2天前
|
SQL 缓存 Java
Java数据库连接池:优化数据库访问性能
【4月更文挑战第16天】本文探讨了Java数据库连接池的重要性和优势,它能减少延迟、提高效率并增强系统的可伸缩性和稳定性。通过选择如Apache DBCP、C3P0或HikariCP等连接池技术,并进行正确配置和集成,开发者可以优化数据库访问性能。此外,批处理、缓存、索引优化和SQL调整也是提升性能的有效手段。掌握数据库连接池的使用是优化Java企业级应用的关键。
|
4天前
|
Java 程序员 编译器
Java中的线程同步与锁优化策略
【4月更文挑战第14天】在多线程编程中,线程同步是确保数据一致性和程序正确性的关键。Java提供了多种机制来实现线程同步,其中最常用的是synchronized关键字和Lock接口。本文将深入探讨Java中的线程同步问题,并分析如何通过锁优化策略提高程序性能。我们将首先介绍线程同步的基本概念,然后详细讨论synchronized和Lock的使用及优缺点,最后探讨一些锁优化技巧,如锁粗化、锁消除和读写锁等。
|
5天前
|
Java 编译器
Java并发编程中的锁优化策略
【4月更文挑战第13天】 在Java并发编程中,锁是一种常见的同步机制,用于保证多个线程之间的数据一致性。然而,不当的锁使用可能导致性能下降,甚至死锁。本文将探讨Java并发编程中的锁优化策略,包括锁粗化、锁消除、锁降级等方法,以提高程序的执行效率。
11 4
|
9天前
|
存储 安全 Java
java多线程之原子操作类
java多线程之原子操作类
|
10天前
|
算法 JavaScript Java
Java多线程+分治求和,太牛了
`shigen`,一位擅长Java、Python、Vue和Shell的博主,分享编程知识和成长体验。在一次面试中因对高并发问题准备不足而受挫,随后深入学习,研究了线程池和经典案例——计算1亿数字的和。采用分治策略,`shigen`实现了Java版的归并排序,并对比了Python的简洁实现。通过多线程和分段求和优化,展示了如何高效解决大数求和问题,引入了分治思想的递归任务来进一步提升性能。未来将探讨`forkjoin`框架。关注`shigen`,每天学习新知识!
17 0
Java多线程+分治求和,太牛了