美文网首页哪些常用的Java知识点
哪些你应该知道的多线程

哪些你应该知道的多线程

作者: 0771609860ee | 来源:发表于2019-11-21 17:24 被阅读0次

    分类

    Java多线程实现方式主要有四种:
            1. 继承Thread类
            2. 实现Runnable接口
            3. 实现Callable接口通过FutureTask包装器来创建Thread线程
            4. 使用ExecutorService、Callable、Future实现有返回结果的多线程。
        其中前两种方式线程执行完后都没有返回值,后两种是带返回值的。
    

    普通多线程

        1. 继承Thread类创建线程
            Thread类再内部实现了Runnable接口,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法(currentThread.start())。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。创建一个类继承 Thread类,并在run()方法中写上自己的业务代码。
    

    demo

    package threads;
    
    import java.util.Date;
    
    /**
     * @author liaowei
     * @date 2019-11-21 15:55:57
     * @decription
     */
    public class ThreadTest {
    
        // 基本多线程
        public static void main(String[] args) {
            MyThread myThread1 = new MyThread();
            MyThread myThread2 = new MyThread();
            myThread1.start();
            myThread2.start();
        }
    }
    
    class MyThread extends Thread {
        public void run() {
            System.out.println("MyThread.run()" + new Date());
        }
    }
    
    
    
    1. 实现Runnable接口创建线程 ,可以实现一个Runnable接口
    package threads;
    
    import java.util.Date;
    
    /**
     * @author liaowei
     * @date 2019-11-21 15:59:50
     * @decription
     */
    public class ThreadImpRun implements Runnable {
    
        //接受参数
        private String params;
    
        //必须存在的构造函数,不然获取不到参数
        public ThreadImpRun(String params) {
            this.params = params;
        }
    
        @Override
        public void run() {
            System.out.println("欢迎关注公众号:【" + this.params + "】");
        }
    
        public static void main(String[] args) {
            ThreadImpRun myThread = new ThreadImpRun("一抹浮云");
            Thread thread = new Thread(myThread);
            thread.start();
        }
    }
    
    

    实用多线程

    1.future模式:并发模式的一种,可以有两种形式,即无阻塞和阻塞,分别是isDone和get。其中Future对象用来存放该线程的返回值以及状态。
    ①、创建Callable接口的实现类,并实现call()方法,改方法将作为线程执行体,且具有返回值。
    ②、创建Callable实现类的实例,使用FutrueTask类进行包装Callable对象,FutureTask对象封装了Callable对象的call()方法的返回值
    ③、使用FutureTask对象作为Thread对象启动新线程。
    ④、调用FutureTask对象的get()方法获取子线程执行结束后的返回值。

    package threads;
    
    import com.alibaba.fastjson.JSONObject;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.FutureTask;
    
    /**
     * @author liaowei
     * @date 2019-11-21 16:03:25
     * @decription
     */
    public class DempCallable<V> {
    
        public static void main(String[] args) {
            JSONObject params = new JSONObject();
            params.put("key", "  一抹浮云   ");
            Callable<JSONObject> callable = new Test<>(params);
            //由Callable<JSONObject>创建一个FutureTask<JSONObject>对象:
            FutureTask<JSONObject> oneTask = new FutureTask<>(callable);
            //FutureTask<Integer>是一个包装器,它通过接受Callable<Integer>来创建,它同时实现了Future和Runnable接口。
            //由FutureTask<Integer>创建一个Thread对象:
            Thread oneThread = new Thread(oneTask);
            oneThread.start();
            //至此,一个线程就创建完成了。
        }
    }
    
    class Test<V> implements Callable<V> {
        private JSONObject params;
    
        public Test(JSONObject params) {
            this.params = params;
        }
    
        @Override
        public V call() throws Exception {
            System.out.println("公众号【" + params.get("key")+"】");
            return null;
        }
    }
    
    
    1. ExecutorService、Callable、Future三个接口实际上都是属于Executor框架。返回结果的线程是在JDK1.5中引入的新特征,我们可以轻松获取多线程处理结果。可返回值的任务必须实现Callable接口。无返回值的任务必须实现Runnable接口。执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了。注意:get方法是阻塞的,即:线程无返回结果,get方法会一直等待。结合线程池接口ExecutorService就可以实现返回结果的多线程.
    package threads;
    
    import com.alibaba.fastjson.JSONObject;
    import org.apache.commons.lang3.StringUtils;
    
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;
    import java.util.concurrent.*;
    
    public class ExecutorsTest {
        public static void main(String[] args) {
            Date date1 = new Date();
    
            List<JSONObject> datas = new ArrayList<>();
            for (int i = 0; i < 10; i++) {
                JSONObject data = new JSONObject();
                data.put("key", "欢迎关注公众号:【一抹浮云】" + i);
                datas.add(data);
            }
    
            int taskSize = 5;
            // 创建一个线程池 :
            /**
             * 线程池有如下创建方式,根据实际需要选择合适的方式:
             * newSingleThreadExecutor():它的特点在于工作线程数目被限制为 1,操作一个无界的工作队列,所以它保证了所有任务的都是被顺序执行,最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目;
             * newCachedThreadPool():它是一种用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置的时间超过 60 秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用 SynchronousQueue 作为工作队列;
             * newFixedThreadPool(int nThreads):重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有 nThreads 个工作线程是活动的。这意味着,如果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现;如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目 nThreads;
             * newSingleThreadScheduledExecutor():创建单线程池,返回 ScheduledExecutorService,可以进行定时或周期性的工作调度;
             * newScheduledThreadPool(int corePoolSize):和newSingleThreadScheduledExecutor()类似,创建的是个 ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程;
             * newWorkStealingPool(int parallelism):这是一个经常被人忽略的线程池,Java 8 才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序;
             * ThreadPoolExecutor():是最原始的线程池创建,上面1-3创建方式都是对ThreadPoolExecutor的封装。
             */
            ExecutorService pool = Executors.newFixedThreadPool(taskSize);
            // 根据自己的需要创建任务数量:比如你有10万条数据需要处理,可以将这些数据分组,建立分组基数  100000/10(任务数量:根据机器的空闲cpu来定)
    
            //注意: 因为事测试 就不造太多数据,MyCallable中所有的params都用datas。实际开发中,应该是对datas进行分组,然后再根据分组数量创建任务数量
            List<Future> list = new ArrayList<>();
            for (int i = 0; i < taskSize; i++) {
                Callable c = new MyCallable(i, datas);
                // 执行任务并获取Future对象
                Future f = pool.submit(c);
                // System.out.println(">>>" + f.get().toString());
                list.add(f);
            }
            // 关闭线程池
            pool.shutdown();
    
            // 获取所有并发任务的运行结果
            for (Future f : list) {
                // 从Future对象上获取任务的返回值,并输出到控制台
                //捕获异常
                try {
                    System.out.println("输出任务运行结果:" + f.get().toString());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
            Date date2 = new Date();
            System.out.println("----程序结束运行----,程序运行时间【"
                    + (date2.getTime() - date1.getTime()) + "毫秒】");
        }
    }
    
    class MyCallable implements Callable<Object> {
        private Integer taskNum;
    
        private List<JSONObject> params;
    
        public MyCallable(Integer taskNum, List<JSONObject> params) {
            this.taskNum = taskNum;
            this.params = params;
        }
    
        public Object call() throws Exception {
            System.out.println("任务-" + taskNum + "-任务启动");
            Date dateTmp1 = new Date();
            String rs = "";
            for (JSONObject param : this.params) {
                if (StringUtils.isNotEmpty(rs)) {
                    rs += "," + param.getString("key");
                } else {
                    rs = param.getString("key");
                }
            }
            Date dateTmp2 = new Date();
            long time = dateTmp2.getTime() - dateTmp1.getTime();
            System.out.println("任务-" + taskNum + "任务结束,返回任务处理结果");
            return taskNum + "-任务返回运行" + rs + "处理任务消耗时间【" + time + "毫秒】";
        }
    }
    

    优势对比:

    继承Thread类和实现Runnable接口、实现Callable接口的区别。
        继承Thread:线程代码存放在Thread子类run方法中
         优势:编写简单,可直接用this.getname()获取当前线程,不必使用Thread.currentThread()方法。
         劣势:Java单继承,继承了Thread类,无法再继承其他类。
        实现Runnable:线程代码存放在接口的子类的run方法中。
         优势:避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
         劣势:相对复杂、访问线程必须使用Thread.currentThread()方法、无返回值。
        实现Callable:
         优势:有返回值、避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
         劣势:比较复杂、访问线程必须使用Thread.currentThread()方法

    相关文章

      网友评论

        本文标题:哪些你应该知道的多线程

        本文链接:https://www.haomeiwen.com/subject/dnitwctx.html