多线程实现的方式总结

JAVA多线程实现方式主要有四种:
继承Thread类、
实现Runnable接口、
Callable、Future实现有返回结果的多线程。
使用ExecutorService

一、继承Thread类

启动线程的唯一方法就是通过Thread类的start()实例方法。
start()方法是一个native方法,它将启动一个新线程,并执行run()方法。

/**
 * @author Myli
 * @create 2023-02-19 5:51
 */
public class testmyli {
    
    
    public static void main(String[] args) {
    
    
        MyThread myThread1 = new MyThread();
        myThread1.start();
    }
}

class MyThread extends Thread {
    
    
    public void run() {
    
    
        System.out.println("MyThread.run()");
    }
}

Thread本质上也是实现了Runnable接口的一个实例,
在这里插入图片描述

在这里插入图片描述

二、实现Runnable接口

众所周知,java是单继承的,如果某个类他已经有父类了,就无法直接extends Thread,此时,如果要实现多线程,可以通过实现一个Runnable接口来进行

/**
 * @author Myli
 * @create 2023-02-21 0:27
 */
public class testrunable {
    
    
    public static void main(String[] args) {
    
    
    //new一个MyThread,
        MyThread myThread = new MyThread();
       //new一个Thread
        Thread thread = new Thread(myThread);
        thread.start();
    }
    
    static class MyThread extends OtherClass implements Runnable {
    
    
        public void run() {
    
    
            System.out.println("MyThread.run()");
        }
    }
}

在这里插入图片描述
在这里插入图片描述

三、 实现callable接口

/**
 * @author Myli
 * @create 2023-02-21 0:44
 */
public class testcallable {
    
    
    public static void main(String[] args) {
    
    

        //调用callable 可以有返回值 可以捕获异常
        Callable<String> tc = new TestCallable();

        FutureTask<String> task = new FutureTask<String>(tc);

        new Thread(task).start();
        try {
    
    
            System.out.println(task.get());//获取返回值
        } catch (InterruptedException | ExecutionException e) {
    
    
            e.printStackTrace();
        }
    }

     static class TestCallable implements Callable<String> {
    
    
        @Override
        public String call() throws Exception {
    
    
            System.out.println("实现callable:" + Thread.currentThread().getName());
            return "callable返回";
        }
    }

}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四、线程池

常见的有
定长线程池(FixedThreadPool)
定时线程池(ScheduledThreadPool )
可缓存线程池(CachedThreadPool)
单线程化线程池(SingleThreadExecutor)
完全手动的 (ThreadPoolExecutor)

定长线程池(FixedThreadPool)

// 1. 创建定长线程池对象 & 设置线程池线程数量固定为3
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
    
    
  public void run() {
    
    
     System.out.println("执行任务啦");
  }
};
// 3. 向线程池提交任务
fixedThreadPool.execute(task);

在这里插入图片描述
在这里插入图片描述

定时线程池(ScheduledThreadPool)

在这里插入图片描述

可缓存线程池(CachedThreadPool)

在这里插入图片描述

单线程化线程池(SingleThreadExecutor)

在这里插入图片描述

(ThreadPoolExecutor)

Executors 的 4 个功能线程池虽然方便,但现在已经不建议使用了,而是建议直接通过使用 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

其实 Executors 的 4 个功能线程有如下弊端:

FixedThreadPool 和 SingleThreadExecutor:主要问题是堆积的请求处理队列均采用 LinkedBlockingQueue,
可能会耗费非常大的内存,甚至 OOM。
CachedThreadPool 和 ScheduledThreadPool:主要问题是线程数最大数是 Integer.MAX_VALUE,
可能会创建数量非常多的线程,甚至 OOM。

// 创建线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,
                                             MAXIMUM_POOL_SIZE,
                                             KEEP_ALIVE,
                                             TimeUnit.SECONDS,
                                             sPoolWorkQueue,
                                             sThreadFactory);
// 向线程池提交任务
threadPool.execute(new Runnable() {
    
    
    @Override
    public void run() {
    
    
        ... // 线程执行的任务
    }
});
// 关闭线程池
threadPool.shutdown(); // 设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程
threadPool.shutdownNow(); // 设置线程池的状态为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表

在这里插入图片描述
当线程池的线程数达到最大线程数时,需要执行拒绝策略。拒绝策略需要实现 RejectedExecutionHandler 接口,并实现 rejectedExecution(Runnable r, ThreadPoolExecutor executor) 方法。不过 Executors 框架已经为我们实现了 4 种拒绝策略:

AbortPolicy(默认):丢弃任务并抛出 RejectedExecutionException 异常。
CallerRunsPolicy:由调用线程处理该任务。
DiscardPolicy:丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务。

注意有界队列和无界队列的区别:如果使用有界队列,当队列饱和时并超过最大线程数时就会执行拒绝策略;而如果使用无界队列,因为任务队列永远都可以添加任务,所以设置 maximumPoolSize 没有任何意义。

创建线程对比

1.实现Runnable或Callable接口比继承Thread类的优势

(1)Runnable或Callable接口适合多个线程进行资源共享

(2)java中单一继承,但是多接口实现,提高扩展性

(3)增加程序的健壮性,代码和数据独立

(4)线程池只能放入Runable或Callable接口实现类,不能直接放入继承Thread的类

Callable和Runnable之间的区别

(1) Callable重写的是call()方法,Runnable重写的方法是run()方法

(2) call()方法执行后可以有返回值,run()方法没有返回值

(3) call()方法可以抛出异常,run()方法不可以

(4) 运行Callable任务可以拿到一个Future对象,表示异步计算的结果 。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果

参考

链接: Java 多线程:彻底搞懂线程池
链接: 创建线程的三种方式及区别
部分参考已找不到原出处

猜你喜欢

转载自blog.csdn.net/m0_54765221/article/details/129107166