多线程的几种实现方式

先上总结:

1.使用实现多线程有四种方式:①继承Thread类;②实现Runnable接口;③使用Callable和FutureTask实现有返回值的多线程;④使用ExecutorService和Executors工具类实现线程池(如果需要线程的返回值,需要在线程中实现Callable和Future接口)

2.继承Thread类的优点:简单,且只需要实现父类的run方法即可(start方法中含有run方法,会创建一个新的线程,而run是执行当前线程)。缺点是:Java的单继承,如果对象已经继承了其他的类则不能使用该方法。且不能获取线程的返回值

3.实现Runnable接口优点:简单,实现Runnable接口必须实现run方法。缺点:创建一个线程就必须创建一个Runnable的实现类,且不能获取线程的返CallabTask优点:可以获取多线程的返回值。缺点:每个多线程都需要创建一个Callable的实现类

4.线程池ExecutorService和工具类Executors优点:可以根据实际情况创建线程数量,且只需要创建一个线程池即可,也能够通过Callable和Future接口得到线程的返回值,程序的执行时间与线程的数量紧密相关。缺点:需要手动销毁该线程池(调用shutdown方法)。

尽量不要使用 继承Thread类 和 实现Runnable接口;尽量使用线程池。否则项目导出都是线程。

在上代码:

package com.swain.programmingpearls.thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

/**
 * thread 的几种实现
 */
public class threadTest {
    public static void main (String[] args) {
        //继承thread
        ExtendsThread extendsThread = new ExtendsThread();
        extendsThread.start();

        //实现runnable
        Thread runThread = new Thread(new AchieveRunnable());
        runThread.start();

        //调用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();
        }

        //runable 匿名内部类方式
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("实现Runnable 匿名内部类方式:" + Thread.currentThread().getName());
            }
        }).start();

        //runnable Lamda表达式
        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                System.out.println("Lamda表达式:" + i);
            }
        }).start();

        System.out.println("主线程");

        //创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        for(int i = 0; i<5; i++)
        {
            AchieveRunnable achieveRunnable = new AchieveRunnable();
            try {Thread.sleep(1000);} catch (InterruptedException e) {}
            executorService.execute(achieveRunnable);
        }
        //关闭线程池
        executorService.shutdown();
    }

    /**
     * 继承thread类
     */
    public static class ExtendsThread extends Thread {
        public void run(){
            System.out.println("方法一 继承Thread线程:" + Thread.currentThread().getName());
        }
    }

    /**
     * 实现runnable
     */
    public static class AchieveRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("方法二 实现Runnable:" + Thread.currentThread().getName());
        }
    }

    /**
     * 通过Callable和FutureTask创建线程
     */
    public static class TestCallable implements Callable<String> {
        @Override
        public String call() throws Exception {
            System.out.println("方法三 实现callable:" + Thread.currentThread().getName());
            return "我是callable的返回";
        }
    }
}

运行结果:
方法一 继承Thread线程:Thread-0
方法二 实现Runnable:Thread-1
方法三 实现callable:Thread-2
我是callable的返回
实现Runnable 匿名内部类方式:Thread-3
主线程
Lamda表达式:0
Lamda表达式:1
Lamda表达式:2
Lamda表达式:3
Lamda表达式:4
方法二 实现Runnable:pool-1-thread-1
方法二 实现Runnable:pool-1-thread-2
方法二 实现Runnable:pool-1-thread-1
方法二 实现Runnable:pool-1-thread-2
方法二 实现Runnable:pool-1-thread-1

 

关于Concurrent包

concurrent包是在AQS的基础上搭建起来的,AQS提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架。

线程池参数

我们常用的主要有newSingleThreadExecutor、newFixedThreadPool、newCachedThreadPool、调度等,使用Executors工厂类创建。

newSingleThreadExecutor可以用于快速创建一个异步线程,非常方便。而newCachedThreadPool永远不要用在高并发的线上环境,它用的是无界队列对任务进行缓冲,可能会挤爆你的内存。

我习惯性自定义ThreadPoolExecutor,也就是参数最全的那个。

假如我的任务可以预估,corePoolSize,maximumPoolSize一般都设成一样大的,然后存活时间设的特别的长。可以避免线程频繁创建、关闭的开销。I/O密集型和CPU密集型的应用线程开的大小是不一样的,一般I/O密集型的应用线程就可以开的多一些。

threadFactory我一般也会定义一个,主要是给线程们起一个名字。这样,在使用jstack等一些工具的时候,能够直观的看到我所创建的线程。

监控

高并发下的线程池,最好能够监控起来。可以使用日志、存储等方式保存下来,对后续的问题排查帮助很大。

通常,可以通过继承ThreadPoolExecutor,覆盖beforeExecute、afterExecute、terminated方法,达到对线程行为的控制和监控。

阻塞队列

阻塞队列会对当前的线程进行阻塞。当队列中有元素后,被阻塞的线程会自动被唤醒,这极大的提高的编码的灵活性,非常方便。在并发编程中,一般推荐使用阻塞队列,这样实现可以尽量地避免程序出现意外的错误。阻塞队列使用最经典的场景就是socket数据的读取、解析,读数据的线程不断将数据放入队列,解析线程不断从队列取数据进行处理。

ArrayBlockingQueue对访问者的调用默认是不公平的,我们可以通过设置构造方法参数将其改成公平阻塞队列。

LinkedBlockingQueue队列的默认最大长度为Integer.MAX_VALUE,这在用做线程池队列的时候,会比较危险。

SynchronousQueue是一个不存储元素的阻塞队列。每一个put操作必须等待一个take操作,否则不能继续添加元素。队列本身不存储任何元素,吞吐量非常高。对于提交的任务,如果有空闲线程,则使用空闲线程来处理;否则新建一个线程来处理任务”。它更像是一个管道,在一些通讯框架中(比如rpc),通常用来快速处理某个请求,应用较为广泛。

DelayQueue是一个支持延时获取元素的无界阻塞队列。放入DelayQueue的对象需要实现Delayed接口,主要是提供一个延迟的时间,以及用于延迟队列内部比较排序。这种方式通常能够比大多数非阻塞的while循环更加节省cpu资源。

另外还有PriorityBlockingQueue和LinkedTransferQueue等,根据字面意思就能猜测它的用途。在线程池的构造参数中,我们使用的队列,一定要注意其特性和边界。比如,即使是最简单的newFixedThreadPool,在某些场景下,也是不安全的,因为它使用了无界队列。

CountDownLatch

假如有一堆接口A-Y,每个接口的耗时最大是200ms,最小是100ms。

我的一个服务,需要提供一个接口Z,调用A-Y接口对结果进行聚合。接口的调用没有顺序需求,接口Z如何在300ms内返回这些数据?

此类问题典型的还有赛马问题,只有通过并行计算才能完成问题。归结起来可以分为两类:

  • 实现任务的并行性
  • 开始执行前等待n个线程完成任务

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

CyclicBarrier与其类似,可以实现同样的功能。不过在日常的工作中,使用CountDownLatch会更频繁一些。

信号量

Semaphore虽然有一些应用场景,但大部分属于炫技,在编码中应该尽量少用。

信号量可以实现限流的功能,但它只是常用限流方式的一种。其他两种是漏桶算法、令牌桶算法

Lock && Condition

在Java中,对于Lock和Condition可以理解为对传统的synchronized和wait/notify机制的替代。concurrent包中的许多阻塞队列,就是使用Condition实现的。

End

不管是wait、notify,还是同步关键字或者锁,能不用就不用,因为它们会引发程序的复杂性。最好的方式,是直接使用concurrent包所提供的机制,来规避一些编码方面的问题。

  • 18
    点赞
  • 161
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android实现多线程几种方式有以下几种方法。首先,可以使用Java的Thread类来创建和管理线程。这种方式需要手动创建线程对象,并在run()方法中编写线程的逻辑。其次,可以使用Java的Runnable接口来实现多线程。这种方式需要创建一个实现了Runnable接口的类,并将其作为参数传递给Thread类的构造函数。然后,可以使用Handler类来实现多线程。通过Handler的post()或postDelayed()方法,可以将任务添加到主线程的消息队列中,从而在主线程中执行异步操作。此外,还可以使用AsyncTask类来实现多线程。AsyncTask是Android提供的一个封装好的线程池,可以方便地在后台执行耗时操作,并在主线程中更新UI。通过重写AsyncTask的doInBackground()方法来执行耗时操作,并通过onPostExecute()方法来更新UI。需要注意的是,AsyncTask必须在主线程中创建实例,并且execute()方法也必须在主线程中调用。另外,Android 3.0之后,可以使用executeOnExecutor()方法来实现并发执行多个AsyncTask任务。总结起来,Android实现多线程几种方式包括使用Thread类、Runnable接口、Handler类和AsyncTask类。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* [Android——多线程的多种实现方式](https://blog.csdn.net/abliudede/article/details/104891324)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [Android开发中四种常用的多线程实现方式](https://blog.csdn.net/star_nwe/article/details/130140238)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值