线程与线程池创建详解

前言

近期面试时被问得比较多的线程池,现在进行一个总结

1.创建线程方式

java有以下四种创建多线程的方式
(具体可参考https://blog.csdn.net/jinxinxin1314/article/details/105695860

  • 1:继承Thread类创建线程
  • 2:实现Runnable接口创建线程
  • 3:使用Callable和FutureTask创建线程
  • 4:使用线程池,例如用Executor框架创建线程

第一种方式肯定是差不多淘汰了。因为用继承约束很大。

Callable和Runnable都可以应用于executors。而Thread类只支持Runnable,因此一般使用Callable和Runnable接口

Callable的使用

Callable和Runnable使用差不多, 但是Callable有返回值, 需要用Future接收

在创建线程时,需要callable传入FutureTask,然后FutureTask传入Thread

 FutureTask futureTask = new FutureTask(callable);
 new Thread(futureTask).start();

在线程池接受时,将Callable传入线程池pool,然后pool.submit()的到Future对象,再通过Future对象get()

 ExecutorService pool = Executors.newFixedThreadPool(2);
        // 创建两个有返回值的任务
        Callable c1 = test.new MyCallable("A");
        // 执行任务并获取Future对象
        Future f1 = pool.submit(c1);
		String a = f1.get().toString();
public static void main(String[] args) {
    Callable callable = new Callable() {
        @Override
        public Object call() throws Exception {
            return "this is Callable's message";
        }
    };
    /**
     * 将callable丢进任务里面
     */
    FutureTask futureTask = new FutureTask(callable);

    /**
     * 启动线程, 执行任务
     */
    new Thread(futureTask).start();
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    try {
        System.out.println("执行任务后的结果: " + futureTask.get());
    } catch (ExecutionException e) {
        e.printStackTrace();
    }  catch (InterruptedException e) {
        e.printStackTrace();
    }
}

2.创建线程Demo

package thread;
 
import java.util.concurrent.*;
 
public class ThreadTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
//      创建线程的第一种方法
        Thread1 thread1 = new Thread1();
        thread1.start();
 
//      创建线程的第二种方法
        Thread2 thread2 = new Thread2();
        Thread thread = new Thread(thread2);
        thread.start();
 
//      创建线程的第三种方法
        Callable<String> callable = new Thread3();
        FutureTask<String> futureTask = new FutureTask<>(callable);
        Thread thread3 = new Thread(futureTask);
        thread3.start();
        String s = futureTask.get();
        System.out.println(s);
 
//      创建线程的第四种方法
        Executor executor = Executors.newFixedThreadPool(5);
        executor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread()+"创建线程的第四种方法");
            }
        });
        ((ExecutorService) executor).shutdown();
 
    }
}
class Thread1 extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread()+"创建线程的第一种方法");
    }
}
 
class Thread2 implements Runnable {
 
    @Override
    public void run() {
        System.out.println(Thread.currentThread()+"创建线程的第二种方法");
    }
}
 
class Thread3 implements Callable<String> {
 
    @Override
    public String call() throws Exception {
        return Thread.currentThread()+"创建线程的第三种方法";
    }
}

3.Runnable接⼝和Callable接⼝

Runnable 接⼝不会返回结果或抛出检查异常,但是 Callable 接⼝可以。

通常情况下线程无返回值用Runnable,有返回值用Callable

public interface Runnable {
 /**
 * 被线程执⾏,没有返回值也⽆法抛出异常
 */
 public abstract void run();
}
public interface Callable<V> {
 /**
 * 计算结果,或在⽆法这样做时抛出异常。
 * @return 计算得出的结果
 * @throws 如果⽆法计算结果,则抛出异常
 */
 V call() throws Exception; }

4.execute()和submit()方法

1.execute() ⽅法⽤于提交不需要返回值的任务,所以⽆法判断任务是否被线程池执⾏成功与否;

2.submit() ⽅法⽤于提交需要返回值的任务。线程池会返回⼀个 Future 类型的对象,通过这个 Future 对象可以判断任务是否执⾏成功,并且可以通过 Future 的 get() ⽅法来获取返回值, get() ⽅法会阻塞当前线程直到任务完成,⽽使⽤ get(long timeout,TimeUnit unit) ⽅法则会阻塞当前线程⼀段时间后⽴即返回,这时候有可能任务没有执⾏完。

3.submit方法可以接受Runnable对象或者是Callable对象而executor只能接受Runnable对象。

submit方法接受Callable对象和接受Runnable对象的区别

参考文章

http://kaimingwan.com/post/java/cong-runnabledao-callabletan-dao-executorserverde-submitfang-fa-he-executefang-fa

之前说过Runnable对象调用run后没有返回结果。所以任务完成后,虽然会返回Future对象,但是实际上调用get方法得到的是null。而如果是Callable对象作为参数给submit方法,那么最后完成任务后的Future对象中可以get到call得结果。

5.submit结合Callable使用

使用场景:多个有返回值的线程进行取值https://jingyan.baidu.com/article/6525d4b13b1f0fac7d2e9431.html

public class CallableFutureTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        CallableFutureTest test = new CallableFutureTest();
        // 创建一个线程池
        ExecutorService pool = Executors.newFixedThreadPool(2);
        // 创建两个有返回值的任务
        Callable c1 = test.new MyCallable("A");
        Callable c2 = test.new MyCallable("B");
        // 执行任务并获取Future对象
        Future f1 = pool.submit(c1);
        Future f2 = pool.submit(c2);
        // 从Future对象上获取任务的返回值,并输出到控制台
        System.out.println(">>>" + f1.get().toString());
        System.out.println(">>>" + f2.get().toString());
        // 关闭线程池
        pool.shutdown();
    }

    class MyCallable implements Callable {
        private String oid;
        MyCallable(String oid) {
            this.oid = oid;
        }

        public Object call() throws Exception {
            return oid + "任务返回的内容";
        }
    }
}

6.线程池核心参数:

核心线程数corePoolSize

最大线程数maximumPoolSize

缓冲队列workQueue

1.小于corePoolSize时,新建线程执行任务

2.大于等于corePoolSize时,新插入的任务进入workQueue排队(如果workQueue长度允许)

3.workQueue也满了,但是小于maximumPoolSize,对于新加入的任务,新建线程

4.workQueue满了,并且池中正在运行的线程数等于maximumPoolSize,对于新加入的任务,执行拒绝策略(线程池默认的拒绝策略是抛异常)

7.线程池构造函数

public ThreadPoolExecutor(int corePoolSize,
                       int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,                  
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
    
}

8.线程池构造demo

ThreadPoolExecutor executor = new ThreadPoolExecutor(
 5,
 10,
 1L,
 TimeUnit.SECONDS,
 new ArrayBlockingQueue<>(10),
 new ThreadPoolExecutor.CallerRunsPolicy());
//处理策略默认抛出异常

9.线程池使用实例

1.编写MyRunnable.class实现Runnable接口创建线程

package com.apollo.demo2;
import java.util.Date;

public class MyRunnable implements Runnable {
    private String command;
    public MyRunnable(String s) {
        this.command = s;
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " Start.Time = " + new Date());
                processCommand();
        System.out.println(Thread.currentThread().getName() + " End.Time = " + new Date());
    }
    private void processCommand() {
        try {
            Thread.sleep(5000);

            } catch (InterruptedException e) {
                    e.printStackTrace();
                    }
    }
            @Override
            public String toString() {
                    return this.command;
                    }
}

2.编写ThreadPoolExecutorDemo.class创建线程池executor,将新创建线程当作参数传入executor.execute()

package com.apollo.demo2;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExecutorDemo {
    private static final int CORE_POOL_SIZE = 5;
    private static final int MAX_POOL_SIZE = 10;
    private static final int QUEUE_CAPACITY = 100;
    private static final Long KEEP_ALIVE_TIME = 1L;
    public static void main(String[] args) {
        //使⽤阿⾥巴巴推荐的创建线程池的⽅式
        //通过ThreadPoolExecutor构造函数⾃定义参数创建
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(QUEUE_CAPACITY),
                new ThreadPoolExecutor.CallerRunsPolicy());
        for (int i = 0; i < 10; i++) {
            //创建WorkerThread对象(WorkerThread类实现了Runnable 接⼝)
            Runnable worker = new MyRunnable("" + i);
            //执⾏Runnable
            executor.execute(worker);
        }
        //终⽌线程池
        executor.shutdown();
        while (!executor.isTerminated()) {
        }
        System.out.println("Finished all threads");
    }
}

可以看到我们上⾯的代码指定了:

  1. corePoolSize : 核⼼线程数为 5。

  2. maximumPoolSize :最⼤线程数 10

  3. keepAliveTime : 等待时间为 1L。

  4. unit : 等待时间的单位为 TimeUnit.SECONDS。

  5. workQueue :任务队列为 ArrayBlockingQueue ,并且容量为 100;

  6. handler :饱和策略为 CallerRunsPolicy 。

10.缓冲队列ArrayBlockingQueue和LinkedBlockingQueue异同

参考文章https://www.cnblogs.com/lianliang/p/5765349.html

相同:

1、LinkedBlockingQueue和ArrayBlockingQueue都实现了BlockingQueue接口;

2、LinkedBlockingQueue和ArrayBlockingQueue都是可阻塞的队列

内部都是使用ReentrantLock和Condition来保证生产和消费的同步;

当队列为空,消费者线程被阻塞;当队列装满,生产者线程被阻塞

不同:

1.锁机制不同

LinkedBlockingQueue中的锁是分离的,生产者的锁PutLock,消费者的锁takeLock

而ArrayBlockingQueue生产者和消费者使用的是同一把锁;

2、他们的底层实现机制也不同

LinkedBlockingQueue内部维护的是一个链表结构

imgimg

在生产和消费的时候,需要创建Node对象进行插入或移除,大批量数据的系统中,其对于GC的压力会比较大

而ArrayBlockingQueue内部维护了一个数组

img

在生产和消费的时候,是直接将枚举对象插入或移除的,不会产生或销毁任何额外的对象实例

3、构造时候的区别

LinkedBlockingQueue有默认的容量大小为:Integer.MAX_VALUE,当然也可以传入指定的容量大小

ArrayBlockingQueue在初始化的时候,必须传入一个容量大小的值

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值