java实现多线程的四种方式及速度比较

Java多线程的实现方式主要有以下几种:

  1. 继承Thread类:可以通过继承Thread类并重写run()方法来创建线程。需要注意的是,Java中不能直接继承多个类,因此这种方式不能同时继承其他类。

  2. 实现Runnable接口:可以通过实现Runnable接口并实现run()方法来创建线程。这种方式可以避免由于Java的单继承限制而带来的问题,并且能够更好地支持多个线程共享同一个资源。

  3. 实现Callable接口:与Runnable接口类似,但是Callable接口的call()方法可以返回执行结果或抛出异常,且必须通过FutureTask类或ExecutorService.submit()方法等方式来执行。

  4. 使用线程池:Java提供了Executor框架,它封装了线程池的创建和管理,使得开发者可以更方便地管理多个线程,避免线程过多或线程资源浪费等问题,它可以管理多个线程并且可以复用已经创建的线程,避免了频繁地创建和销毁线程的开销。

    Executor(执行器)类:Executor有许多静态工厂方法用来构建线程池,其定义了线程池的基本行为,它的作用主要是为我们提供任务与执行机制(包括线程使用和调度细节)之间的解耦;

    ExecutorService继承了Executor,其提供了更多操作多线程的方法,比如submit、invokeAll、invokeAny等,可以提交任务,还可以等待任务执行完成。

    AbstractExecutorService则是ExecutorService实现类,它提供了ExecutorService接口的默认实现

    ThreadPoolExecutorScheduledThreadPoolExecutor继承AbstractExecutorService,这样就可以减少实现的复杂度。

    (PS:默认实现是指在接口中定义的方法的实现。在Java 8之前,接口中不能有方法的实现,但是Java 8引入了默认方法,它允许在接口中定义方法的实现。这些方法可以被实现接口的类继承或重写。)

    Executors(执行器工具类)Executors工厂类一共可以创建四种类型的线程池,分别是newFixedThreadPool、newSingleThreadExecutor、newCachedThreadPool和newScheduledThreadPool。

    • ExecutorService newFixedThreadPool() : 创建固定大小的线程池
    • ExecutorService newCachedThreadPool() : 缓存线程池,线程池的数量不固定,可以根据需求自动的更改数量。
    • ExecutorService newSingleThreadExecutor() : 创建单个线程池。线程池中只有一个线程
    • ScheduledExecutorService newScheduledThreadPool() : 创建固定大小的线程,可以延迟或定时的执行任务。

    常用方法:

    在Executor框架中,可以使用ExecutorService接口来管理线程池。ExecutorService接口中有两个常用的方法submitinvokeAll

    submit方法用于向线程池提交一个Callable或Runnable任务,并返回一个Future对象,可以通过该对象获取任务的执行结果或取消任务的执行。

    invokeAll方法用于向线程池提交一组Callable任务,并返回一个包含所有任务执行结果的Future列表。当所有任务都执行完成后,该方法才会返回。

    应用场景选择:

    submit()invokeAll() 都是 ExecutorService 接口中定义的方法,用于向线程池提交任务。它们的主要区别在于返回值和异常处理:

    submit() 方法用于提交一个 CallableRunnable 任务,并返回一个表示该任务待处理结果的 Future 对象。通过 Future 对象可以判断任务是否完成、取消任务、获取任务的执行结果等。

    invokeAll() 方法用于同时提交多个 Callable 任务,并返回一个 Future 对象列表。这个方法会阻塞当前线程,直到所有任务都执行完成或者超时,然后返回所有任务的执行结果列表。

    在性能方面,submit()invokeAll() 的差别不大,因为它们都是基于线程池实现的。但是,它们的使用场景不同,如果需要同时提交多个任务,并等待所有任务完成后再进行处理,使用 invokeAll() 会更加方便和高效;如果只需要提交单个任务,并处理任务的执行结果,那么使用 submit() 更为适合。

在使用Java进行多线程编程时,需要注意以下几点:

  1. 线程安全:在多线程环境下,如果多个线程同时修改同一个数据或资源,可能会导致数据不一致或资源竞争的问题。因此,需要确保多个线程能够正确地访问和修改共享的数据或资源。可以使用同步机制(如synchronized关键字)或使用线程安全的数据结构(如ConcurrentHashMap)来保证线程安全。
  2. 线程间通信:在多线程环境下,多个线程之间可能需要进行数据交换和协作。可以使用wait()、notify()和notifyAll()等方法来实现线程间的通信。
  3. 死锁:死锁是指多个线程在互相等待对方释放资源时,导致程序无法继续执行的情况。为避免死锁,可以使用避免策略(如避免嵌套锁,按照固定顺序获取锁)或使用死锁检测机制。
  4. 性能:多线程编程可能会影响程序的性能,因为线程间的切换需要时间和开销。为了提高程序的性能,可以考虑使用线程池和使用异步编程模型。
  5. 异常处理:多线程环境下,可能会出现一些难以调试的异常情况,如线程中断、InterruptedException和ConcurrentModificationException等。需要编写健壮的代码,并考虑如何处理异常情况。

例子

下面将以计算1到100000区间的素数作为例子,演示如何使用多线程编程:

1、继承Thread类:-耗时81毫秒

package com.mjf.base1;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//继承Thread类:
// 可以通过继承Thread类并重写run()方法来创建线程。
// 需要注意的是,Java中不能直接继承多个类,因此这种方式不能同时继承其他类。
//下面第一种方式(118毫秒)无法做到线程同步,因为创建了两个ThreadDeam实例,应该创建一个ThreadDeam实例,再创建两个thread实例来保证线程安全,就像第二种(81毫秒)
public class TestThread_extends_Thread {
    public static void main(String[] args){
        long startTime = System.currentTimeMillis(); // 程序开始执行的时间戳
//        第一种方式
//        ThreadDeam dd = new ThreadDeam("线程一","嚯嚯嚯");
//        dd.start();
//        ThreadDeam dd1 = new ThreadDeam("线程二","哈哈哈");
//        dd1.start();

//        第二种方式
        ThreadDeam dd2 = new ThreadDeam("线程一","嚯嚯嚯");
        Thread t1 = new Thread(dd2, "Thread1");
        Thread t2 = new Thread(dd2, "Thread2");
        t1.start();
        t2.start();

        //调用Thread.join()方法等待其他线程执行完毕
        try {
//            dd.join();
//            dd1.join();
            t1.join();
            t2.join();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis(); // 程序结束执行的时间戳
        System.out.println("程序执行时间为:" + (endTime - startTime) + "毫秒");
    }
}
class ThreadDeam extends Thread{

    private String arg;

    private int count = 100000;
    private int num = 1;
    private Lock lock = new ReentrantLock();

    public ThreadDeam(String name){
        super(name);
    }
    public ThreadDeam(String name,String arg){
        super(name);
        this.arg = arg;
    }

    @Override
    public void run(){
        /*求3到100之间的素数,素数:素数是指除了1和它本身以外没有其他正整数能够整除它的大于1的整数。换句话说,素数是只能被1和它本身整除的正整数。*/
        boolean flag =false;
        System.out.println(arg);
        while(num<count){
            flag = false;
            for(int j = 2;j<=Math.sqrt(num);j++){
                if(num%j == 0){
                    flag = true;
                    break;
                }
            }
            if(flag == false){

                System.out.println(Thread.currentThread().getName()+",素数:"+num+" ");
            }
            lock.lock();
            try{
                num++;
            } finally {
                lock.unlock();
            }
        }
    }
}

2、实现Runnable接口-耗时53毫秒

/*多个线程同时计算1到100000之间的素数,使用了synchronized实现方法级同步,用时53毫秒*/
public class TestThread_impl_runnable {
    public static void main(String[] args){
        long startTime = System.currentTimeMillis(); // 程序开始执行的时间戳
        Runnabledemo dd = new Runnabledemo(1,100000); //多个线程调用同一个实例,则会共享该实例的变量num、endnum
        Thread t1 = new Thread(dd,"线程一:");
        Thread t2 = new Thread(dd,"线程二二:");
        Thread t3 = new Thread(dd,"线程三三三:");
        t1.start();
        t2.start();
        t3.start();

        //调用Thread.join()方法等待其他线程执行完毕
        try {
            t1.join();
            t2.join();
            t3.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis(); // 程序结束执行的时间戳
        System.out.println("程序执行时间为:" + (endTime - startTime) + "毫秒");
    }
}

class Runnabledemo implements Runnable{
    private int num;
    private int endnum;

    public Runnabledemo(int startnum,int endnum){
        this.num = startnum;
        this.endnum = endnum;
    }
    @Override
    public void run(){
        boolean flag = false;
        System.out.println(Thread.currentThread().getName()+" 素数: 2");
        synchronized (this) {
        while (num <= endnum) {
                if (isPrime(num)) {
                    System.out.println(Thread.currentThread().getName() + " 素数:" + num);
                }
                num++;
            }
        }
    }

    private boolean isPrime(int n) {
        if (n < 2) {
            return false;
        }
        for (int i = 2; i <= Math.sqrt(n); i++) {
            if (n % i == 0) {
                return false;
            }
        }
        return true;
    }

}

3、实现Callable接口-耗时51毫秒

package com.mjf.base1;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class TestThread_impl_callable {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        long startTime = System.currentTimeMillis(); // 程序开始执行的时间戳
        CallableDemo callableDemo = new CallableDemo();

//        FutureTask是一个实现了Future接口和Runnable接口的类,它可以被用来包装一个Callable或Runnable对象,被Executor执行后,会产生一个结果。
//        FutureTask可以通过get()方法来获取执行结果,也可以通过cancel()方法来取消执行任务。
//        FutureTask可以用于异步执行任务,将耗时的任务提交给线程池后,主线程可以继续执行其他操作,等到结果需要被使用时,可以调用FutureTask的get()方法获取结果,
//        如果结果还未计算出来,则get()方法会阻塞当前线程,直到结果计算出来。
//        FutureTask还提供了一些方法,如isCancelled()方法用来判断任务是否被取消,isDone()方法用来判断任务是否已经完成,等等。

//        Future 和 FutureTask 都是用于异步执行任务并获取其执行结果的工具,但它们的用途和特点有所不同。
//        Future 接口是一个异步任务的抽象,代表了一个异步计算的结果。通过 Future 接口,可以异步获取任务执行结果,或者通过 isDone() 方法判断异步操作是否已经完成。
//        Future 接口定义了一些方法,比如 get() 方法可以阻塞当前线程等待任务完成并返回结果,还有一些方法可以取消任务等等。Future 的特点是轻量级,它只是一个接口,
//        不需要额外的线程或任务队列,可以方便地与多种异步机制配合使用,如 Executor 和 CompletionService 等。
//        FutureTask 类则是 Future 接口的一种实现,它实现了 Future 和 Runnable 接口,因此可以被提交到线程池中执行。
//        与 Future 接口相比,FutureTask 的主要特点是它封装了一个计算任务,可以在 FutureTask 对象创建时传入一个 Callable 接口实例,表示需要异步执行的计算任务。
//        FutureTask 会将这个计算任务提交给线程池执行,并将任务的计算结果保存在 FutureTask 中,可以通过 get() 方法获取。
//        此外,FutureTask 还支持任务的取消和状态查询等操作,具有更丰富的功能和更强的扩展性。
//        因此,可以简单地理解为 Future 是一种轻量级的异步计算结果的封装,而 FutureTask 则是一种更加完整的异步计算任务的封装,支持任务的提交、取消、状态查询等操作。

        FutureTask futureTask = new FutureTask<>(callableDemo);
        FutureTask futureTask2 = new FutureTask<>(callableDemo);

        Thread dd = new Thread(futureTask);
        dd.setName("线程一:");
        dd.start();

        Thread ddd = new Thread(futureTask);
        ddd.setName("线程二:");
        ddd.start();

//        Thread dd2 = new Thread(futureTask2);
//        dd2.setName("线程二:");
//        dd2.start();

        List<Integer> lists = (List<Integer>)futureTask.get();
        for (Integer integer : lists){
            System.out.println("素数:"+integer + " ");
        }

//        List<Integer> lists2 = (List<Integer>)futureTask2.get();
//        for (Integer integer : lists2){
//            System.out.println(dd.getName()+"素数:"+integer + " ");
//        }
        long endTime = System.currentTimeMillis(); // 程序结束执行的时间戳
        System.out.println("程序执行时间为:" + (endTime - startTime) + "毫秒");
    }
}

class CallableDemo implements Callable<List<Integer>>{
    @Override
    public List<Integer> call() throws Exception{
        boolean flag = false;
        List<Integer> lists = new ArrayList<>();
        for(int i = 1 ; i < 100000;i ++){
            flag = false;
            for (int j = 2;j <= Math.sqrt(i); j ++){
                if (i%j == 0){
                    flag = true;
                    break;
                }
            }
            if (flag == false) {
                lists.add(i);
            }
        }
        return lists;
    }
}

4、使用线程池-耗时17毫秒

/*多个线程去计算一段数据区间的素数-1到100000耗时17毫秒-全场最快*/
public class TestThread_ThreadPool {
    public static void main(String[] args) throws InterruptedException, ExecutionException{
        int intstart = 1;
        int intend = 100000;
        int n = intend - intstart;
        int threadNum = 3;
        int subRange = n / threadNum;

        ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
        List<Future<List<Integer>>> results = new ArrayList<>();

        long startTime = System.currentTimeMillis(); // 程序开始执行的时间戳

        //循环提交任务到线程池
        for (int i = 0; i < threadNum; i++) {
            int start = intstart + i * subRange;
            int end = (i == threadNum - 1) ? (intstart + n - 1) : (intstart + (i + 1) * subRange - 1);


            //Future 接口,Future<List<Integer>> 是一个Java泛型类型,表示一个可能会在未来完成并返回结果的异步操作。
            //在这个代码示例中,使用线程池提交任务时,线程池返回一个 Future 对象,其中包含了可能在某个未来时刻计算出的 List<Integer> 类型的结果。
            //使用 Future 对象可以异步获取任务执行结果,或者等待任务执行完成。
            //具体来说,可以通过调用 Future 对象的 get() 方法来获取异步操作返回的结果,或者通过 isDone() 方法判断异步操作是否已经完成。



            Future<List<Integer>> future = executorService.submit(new Callable<List<Integer>>(){
                @Override
                public List<Integer> call() throws Exception {
                    System.out.println(Thread.currentThread().getName() + " ");
                    List<Integer> lists = new ArrayList<>();
                    for(int i = start; i <= end; i++){
                        if (i == 1) {
                            continue;
                        }
                        boolean flag = true;
                        for (int j = 2;j <= Math.sqrt(i);j ++){
                            if (i%j == 0){
                                flag = false;
                                break;
                            }
                        }
                        if (flag == true){
                            lists.add(i);
                        }
                    }
                    return lists;
                }
            });
            results.add(future);
        }

        //等待所有任务执行完成,并将结果汇总到一个List中
        List<Integer> finalResult = new ArrayList<>();
        for (Future<List<Integer>> future : results) {
            finalResult.addAll(future.get());
        }

        System.out.println(finalResult);
        long endTime = System.currentTimeMillis(); // 程序结束执行的时间戳
        System.out.println("程序执行时间为:" + (endTime - startTime) + "毫秒");

        executorService.shutdown();
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值