最近因为业务原因,一个接口不同的字段都要调不同的服务来拿,串行调用肯定会超时。那么只有通过异步调用或者多线程来实现。于是把五花八门的多种实现方式整理了一下。

1.继承Thread 类,实现run 方法 。 用start方法启动

package com.dianping.cip.region.biz.utils;

public class ThreadDemo extends Thread {

    @Override
    public void run(){
        while (true) {
            System.out.println(Thread.currentThread().getName() + " is running ... "); // 打印当前线程的名字
            try {
                Thread.sleep(1000); // 休息1000ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public ThreadDemo(){
        super();
    }

    public ThreadDemo(String threadName){
        super(threadName);
    }
    public static void main(String[] args){
        ThreadDemo thread1 = new ThreadDemo();
        ThreadDemo thread2 = new ThreadDemo("线程2");
        ThreadDemo thread3 = new ThreadDemo("线程3");
        thread1.start();
        thread2.start();
        thread3.start();
        while (true) {
            System.out.println(Thread.currentThread().getName() + " is running ... "); // 打印当前线程的名字
            try {
                Thread.sleep(1000); // 休息1000ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

执行结果:

java 多线程模拟高并发 java多线程demo_多线程

创建的3个线程和主线程一起在跑

java和C++一样,当自己没有实现构造函数时,系统会自动创建一个默认的构造函数(无参构造函数)。 但当自己实现一个构造函数,系统便不再提供构造函数。所以我这里实现了带参的构造函数之后,想再调用无参的构造函数,只能再自己实现一个。

这里是为了改变线程的名字。系统默认的是Thread-0,1,2....

2.实现Runnable接口

public class ThreadTest implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println(Thread.currentThread().getName() + " is running ... "); // 打印当前线程的名字
            try {
                Thread.sleep(1000); // 休息1000ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public static void main(String[] args){
        ThreadTest task = new ThreadTest();
        Thread t1= new Thread(task);
        Thread t2 = new Thread(task);
        t1.start();
        t2.start();
        while (true) {
            System.out.println(Thread.currentThread().getName() + " is running ... "); // 打印当前线程的名字
            try {
                Thread.sleep(1000); // 休息1000ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

这里和上面的区别在于解耦,实现线程任务的接口,线程任务和线程的控制分离,让一个线程任务可以提交给多个线程来执行。

变更线程任务时,只需要改变接口方法就好。

3.内部类实现

new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println(Thread.currentThread().getName() + " is running ... "); // 打印当前线程的名字
                    try {
                        Thread.sleep(1000); // 休息1000ms
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

 

4.定时器 启动定时任务

import org.joda.time.LocalTime;

import java.text.SimpleDateFormat;
import java.util.Timer;
import java.util.TimerTask;

public class TimerDemo {

    private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

    public static void main(String[] args) throws Exception {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(LocalTime.now() +":定时任务执行了");
            }
        }, format.parse("2019-01-19 17:09:00"));
    }

}
/**
* 定时实现,下面是每隔4秒执行一次run方法
*/
  timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(LocalTime.now() +":定时任务执行了");
            }
    }, new Date(),4000);

执行结果:

java 多线程模拟高并发 java多线程demo_java 多线程模拟高并发_02

以上的方式 都没有返回值

5. 实现Callable 接口

通过Future方式可以获取返回值,且可以抛出异常

import org.joda.time.LocalTime;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

public class CallableTest {
    public static void main(String[] args) throws Exception {
        
        Callable<String> call = new Callable<String>() {
            @Override
            public String call() throws Exception {
                System.out.println(LocalTime.now()+"thread start...");
                Thread.sleep(1000);
                return "success";
            }
        };

        FutureTask<String> futureTask = new FutureTask<>(call);
        Thread t = new Thread(futureTask);
        t.start();
        System.out.println(LocalTime.now());
        System.out.println(LocalTime.now()+futureTask.get(500L,TimeUnit.MILLISECONDS));
    }
}

执行结果:

java 多线程模拟高并发 java多线程demo_多线程_03

可以看到。这里我设置500ms后get 。而线程休眠了一秒,所以get会抛异常:java.util.concurrent.TimeoutException

加上try catch 就可以捕获住。将时间加到1500ms,就正常返回了:

java 多线程模拟高并发 java多线程demo_java 多线程模拟高并发_04

6.通过线程池来实现多线程

线程池,jdk有现成的API,通过ExecutorService ,也分实现runnable 和callable 接口

spring也可以通过注解实现多线程(暂时没整理@Async )

executorService 通过submit 提交线程任务

6.1 实现runnable接口, run没有返回值,返回void

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FutureTest1 {

    public static class Task implements Runnable {

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " execute!!!");
        }
    }

    public static void main(String[] args) {

        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            executorService.submit(new Task());
        }

        if (!executorService.isShutdown()) {
            executorService.shutdown();
        }
    }
}

6.2 Future 实现 callable call(),可以返回

Callable位于java.util.concurrent包下

Futrue可以监视目标线程调用call的情况,当你调用Future的get()方法以获得结果时,当前线程就开始阻塞,直接call方法结束返回结果。 

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class FutureTest2 {

    public static class Task implements Callable<String> {

        @Override
        public String call() throws Exception {
            System.out.println(Thread.currentThread().getName() +" execute!!!");
            return "complete";
        }
    }

    public static void main(String[] args) throws InterruptedException,
            ExecutionException {
        List<Future<String>> results = new ArrayList<Future<String>>();
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            results.add(executorService.submit(new Task()));
        }
        for (Future<String> future : results) {
            System.out.println("future get: "+ future.get());
        }

        System.out.println("Main thread complete");

        if (!executorService.isShutdown()) {
            executorService.shutdown();
        }
    }
}

执行结果:

java 多线程模拟高并发 java多线程demo_多线程_05

或者直接内部类的方式

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class FutureTest2 {
    public static void main(String[] args) throws InterruptedException,
            ExecutionException {
        List<Future<String>> results = new ArrayList<Future<String>>();
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            results.add(executorService.submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    System.out.println(Thread.currentThread().getName() +" execute!!!");
                    return "complete";
                }
            }));
        }
        for (Future<String> future : results) {
            System.out.println("future get: "+ future.get());
        }

        System.out.println("Main thread complete");

        if (!executorService.isShutdown()) {
            executorService.shutdown();
        }
    }
}

对比修改下,get的时间,然后try catch住

public class FutureTest2 {
    public static void main(String[] args) throws InterruptedException,
            ExecutionException {
        try{
        List<Future<String>> results = new ArrayList<Future<String>>();
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            results.add(executorService.submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    System.out.println(Thread.currentThread().getName() +" execute!!!");
                    Thread.sleep(1000);
                    return "complete";
                }
            }));
        }
        for (Future<String> future : results) {
            System.out.println("future get: "+ future.get(500L,TimeUnit.MILLISECONDS));
        }

        System.out.println("Main thread complete");

        if (!executorService.isShutdown()) {
            executorService.shutdown();
        }}catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("thead is end of run");
    }
}

执行结果:

java 多线程模拟高并发 java多线程demo_future_06

注意:Java的Future不是真正的异步,其get函数需要阻塞线程。

get方法分为 get() 和 get(500L, TimeUnit.MILLISECONDS) ,无参和有参2种实现。

get(500L, TimeUnit.MILLISECONDS) 在设定时间后get  call的返回值,如果线程还没结束返回,就会抛java.util.concurrent.TimeoutException的异常。

而get() ,会一直阻塞当前线程,直到获取到线程的返回值。

所以实际应用中,一旦依赖的服务返回很慢,用get() 会造成接口超时的,所以应该用 get(500L, TimeUnit.MILLISECONDS) 这样的用法,再用try catch 捕获住,500ms不返回就不再等待,避免接口等待某一个服务而超时。

6.3CountDownLatch

1> CountDownLatch

CountDownLatch是在java1.5被引入的,跟它一起被引入的并发工具类还有CyclicBarrier、Semaphore、ConcurrentHashMapBlockingQueue,它们都存在于java.util.concurrent包下。CountDownLatch这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。

CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。

构造器中的计数值(count)实际上就是闭锁需要等待的线程数量。这个值只能被设置一次,而且CountDownLatch没有提供任何机制去重新设置这个计数值

2> CountDownLatch.await()

与CountDownLatch的第一次交互是主线程等待其他线程。主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。

CountDownLatch.await()方法,调用此方法会一直阻塞当前线程,直到计时器的值为0。也可以设置定时,等待定时之后,会结束当前线程的阻塞。

注意:await 和get一样 也有2种实现:

await()  和 await(long timeout, TimeUnit unit)

await()  是一直等到CountDownLatch的计数器为0 ,调用await方法的线程才会继续执行,所以它是等于保证所有线程跑完,才开始继续执行主线程(调用await方法的线程)

而await(long timeout, TimeUnit unit),是等待设定的时间长度,不管计数器是不是0,都直接继续停止阻塞的状态,让主线程继续往下执行。

做个比喻,ExecutorService 提交的线程任务,会在同一时间开始执行,可以理解为所有子线程一起开始跑,而await()方法保证所有子线程到达终点,才结束“比赛"。

而await(long timeout, TimeUnit unit) 是个曝脾气的裁判,给你们500ms,不管子线程跑到哪都强行结束比赛。

3>  CountDownLatch.countDown()

其他N 个线程必须引用闭锁对象,因为他们需要通知CountDownLatch对象,他们已经完成了各自的任务。这种通知机制是通过 CountDownLatch.countDown()方法来完成的;每调用一次这个方法,在构造函数中初始化的count值就减1。所以当N个线程都调 用了这个方法,count的值等于0,然后主线程就能通过await()方法,恢复执行自己的任务。

注意:countDown()最好在finally模块中使用哈

可以看下面的demo:

import org.joda.time.LocalTime;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

public class FutureTest3 {
    public static void main(String[] args) throws InterruptedException,
            ExecutionException {
        try {
            final CountDownLatch countDownLatch = new CountDownLatch(10);
            List<Future<String>> results = new ArrayList<Future<String>>();
            ExecutorService executorService = Executors.newCachedThreadPool();
            for (int i = 0; i < 5; i++) {
                results.add(executorService.submit(new Callable<String>() {
                    @Override
                    public String call() throws Exception {
                        try {
                            System.out.println(Thread.currentThread().getName() + " " + LocalTime.now() + " execute!!!");
                            Thread.sleep(500);
                        } catch (Exception e) {
                            e.printStackTrace();
                        } finally {
                            countDownLatch.countDown();
                        }
                        return LocalTime.now() + " " + "complete";
                    }
                }));
            }
            for (int i = 0; i < 5; i++) {
                results.add(executorService.submit(new Callable<String>() {
                    @Override
                    public String call() throws Exception {
                        try {
                            System.out.println(Thread.currentThread().getName() + " " + LocalTime.now() + " execute!!!");
                            Thread.sleep(2000);
                        } catch (Exception e) {
                            e.printStackTrace();
                        } finally {
                            countDownLatch.countDown();
                        }
                        return LocalTime.now() + " " + "complete";
                    }
                }));
            }
            countDownLatch.await();
            // countDownLatch.await(500L, TimeUnit.MILLISECONDS);
            System.out.println(LocalTime.now());
            for (Future<String> future : results) {
                System.out.println(LocalTime.now() + "future get: " + future.get());
            }

            System.out.println("Main thread complete");

            if (!executorService.isShutdown()) {
                executorService.shutdown();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("thead is end of run");
    }
}

执行结果:

java 多线程模拟高并发 java多线程demo_excutorService_07

这里使用await(),可以看打印的时间发现,主线程是在最长的子线程跑完,才开始执行的。

再看看,使用countDownLatch.await(500L, TimeUnit.MILLISECONDS) 。执行结果如下:

java 多线程模拟高并发 java多线程demo_多线程_08

但是细心的会发现,即使没有await 那么久,但使用get()方法,主线程还是等待了2000ms,所以光用 await(500L, TimeUnit.MILLISECONDS) 不能完全解决超时的问题。 

get也得使用带参的,才能定时返回,记住try catch

注意坑:

1.同时多线程调用多个服务,使用countDownLatch.await(500L,TimeUnit.MILLISECONDS);

的确会立刻结束阻塞状态,让当前主线程继续执行。但后面获取返回值如果使用的get() ,那么恭喜你,主线程依然会等待子线程的返回值,主线程还是会阻塞!所以为了接口够快,必须也要使用对应的get(500L,TimeUnit.MILLISECONDS)

不然await做的会是无用功!!!!

2.使用await()的话,它会等所有子线程一直跑完,才继续执行,get 加不加时间参数都无意义,肯定等到返回值了。