当前位置: 代码迷 >> 综合 >> Java多线程实现(小白版)
  详细解决方案

Java多线程实现(小白版)

热度:17   发布时间:2024-01-29 12:18:02.0

一、前言

      Java中的多线程无疑是编程中的非常重要的一环、可以并行执行任务,达到提高效率、节省时间的目的。我们可以直接通过继承Thread类或者实现Runnable接口创建多线程。当然线程少的话是可以直接创建线程的,但是当线程多时可能造成系统内存OOM等,因此有必要使用线程池、线程池和连接池的概念类似、是通过维护一定数量的线程来达到多个线程复用的目的。 

使用多线程时、有时候我们需要保证前面的多线程全部执行完毕再执行后面的任务,如何实现让我们一起来看看

二、多线程创建

     1、 实现thread类,重写run()方法,start()方法开启线程

public class MyThread extends Thread {@Overridepublic void run() {System.out.println("实现Thread类创建多线程");}public static void main(String[] args) {MyThread t = new MyThread();t.start();}
}

    2、实现Runnable接口、实现run()方法,同样可以直接创建一个new Thread(xxxRunnable).start()开启线程。该方式使用较频繁、因为Java是单继承、多实现、实现Runnable接口还可以实现其他的接口,而继承了Thread类之后就不能再继承其他类了,有很大的局限性。实际上jdk8之后lamda表达式可以简化这种多线程创建的方式,下面可见示例:

public class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("我是直接实现Runnable接口的多线程实现方式!");}public static void main(String[] args) {Thread t2 = new Thread(new MyRunnable());t2.start();
//     lamda表达式简化简化书写new Thread(() -> System.out.println("我实质也是实现Runnable接口的匿名对象--lamda表达式实现!")).start();}
}

 3、使用线程池可以提高线程的使用效率、避免反复的创建线程、销毁线程带来的开销。创建线程池之后以后如果需要使用线程池的时候,可以直接从线程池里取出即可。

public class MyThreadExecutor {int corePoolSize = 10;    // 线程池的基本大小int maximumPoolSize = 30;  // 线程池最大大小int keepAliveTime = 10;  // 线程存活保持时间ThreadPoolExecutor executor =  new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,TimeUnit.SECONDS,new LinkedBlockingQueue<>());public void submit(Runnable task){executor.submit(task);}
}

比如我们前面创建了4个多线程,我们想统计这4个多线程全部执行完之后所需要的时间、这时候就必须等待全部线程执行完毕再计算耗时,这里使用到CountDownLatch,每个线程执行完毕之后,调用其countDown()方法,数量就会减少1,当计数器为0时就表征全部的多线程执行完毕,这里给出实例。首先给出测试方法实例

public class AsyncService {public void testMethod1(){try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("我是异步方法1");}public void testMethod2(){try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("我是异步方法2");}public void testMethod3(){try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("我是异步方法3");}public void testMethod4(){try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("我是异步方法4");}
}

接下来我们调用这四个方法,并统计全部时间执行完毕所需时间:

public class MultiThreadCsdnTest {public static void main(String[] args) {long startTime = System.currentTimeMillis();CountDownLatch countDownLatch = testMyExecutorPool();try {countDownLatch.await();}catch (InterruptedException  e){e.printStackTrace();}System.out.println("四个线程全部执行完毕");long endTime = System.currentTimeMillis();System.out.println("4个线程全部执行完所需时间为:" + (endTime - startTime));}public static CountDownLatch testMyExecutorPool(){AsyncService service = new AsyncService();CountDownLatch countDownLatch = new CountDownLatch(4);MyThreadExecutor myThreadExecutor = new MyThreadExecutor();myThreadExecutor.submit(() -> {service.testMethod1();countDownLatch.countDown();});myThreadExecutor.submit(() -> {service.testMethod2();countDownLatch.countDown();});myThreadExecutor.submit(() -> {service.testMethod3();countDownLatch.countDown();});myThreadExecutor.submit(() -> {service.testMethod4();countDownLatch.countDown();});return countDownLatch;}
}
result:
我是异步方法1
我是异步方法3
我是异步方法4
我是异步方法2
四个线程全部执行完毕
方法执行的时间为:2067

可以发现,四个线程全部执行完毕之后,才统计的方法总执行时间,总用时2067ms,符合预期。如果不使用多线程,依次顺序执行上述4个方法那么总耗时就是8000ms.

    @Testpublic void testOneThread() {AsyncService service = new AsyncService();long startTime = System.currentTimeMillis();service.testMethod1();service.testMethod2();service.testMethod3();service.testMethod4();long endTime = System.currentTimeMillis();System.out.println("总耗时:"+(endTime-startTime));}
result: 
我是异步方法1
我是异步方法2
我是异步方法3
我是异步方法4
总耗时:8000

三、小结

我们在项目中很多场景都要使用多线程,当创建的线程不多时可以直接创建而不必使用线程池、但是当线程较多时为了提高效率和方便管理最好是首先创建线程池,然后再使用。创建线程池除了上文提到的基本创建方式外,也可以创建一个固定数目的线程池(Executors.newFixedThreadPool(threadCount))或者其他方式等。最后很多场景下我们需要等所有的多线程执行完毕之后再执行后面的任务,这时候可以选用一个计数器,每个线程执行完毕后计数器减少1,只有当计数器为0时才执行后面的内容,否则一直处于悬挂状态。