你应该知道的 Java多线程及线程池

分类

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()方法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章