线程和线程池

1.线程

创建一个线程有两种方法,一种是继承Thread类,一种是时间Runnable接口。

当我们Thread t = new Thread()的时候,Thread类内部会调用init方法

init(null, null, "Thread-" + nextThreadNum(), 0);

init方法传入空的线程组,Runnable对象,和默认的线程名称,即以 thread- 开头,从0开始依次递增的线程名称

private void init(ThreadGroup g, Runnable target, String name,long stackSize) {
    init(g, target, name, stackSize, null, true);
}

通过方法重载,如果线程名称为null抛出异常。

如果传入的线程组g是null,Thread会默认获取父线程的线程组作为该线程的线程组。

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }

    this.name = name;
    Thread parent = currentThread();
    SecurityManager security = System.getSecurityManager();
    if (g == null) {
        if (security != null) {
            g = security.getThreadGroup();
        }
        if (g == null) {
            g = parent.getThreadGroup();
        }
    }
    。。。
}

在我们调用start()方法时,如果传递的Runable为空,或者没有复写run方法,这个Thread不会调用任何东西。

如果传递了Runnable的实例,或者复写了run方法,则会执行逻辑。

@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

参数stacksize代表该线程占用stack大小,如果没有指定,默认为0,0代表忽略该参数。在一些操作系统中该参数无效。

2.Thread的一些API

join:Thread类中的join方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行

如下代码:

public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                IntStream.range(1,50).forEach(i ->
                        System.out.println(Thread.currentThread().getName()+"---"+i));
            }
        };
        t.start();
//        try {
//            t.join();
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        Optional.of("main bengin").ifPresent(System.out::println);
        IntStream.range(1,50).forEach(i ->
                System.out.println(Thread.currentThread().getName()+"---"+i));
    }

创建一个线程t,和main线程做同样的事情。当把 t.join()方法注释时,程序的结果是两个线程相互争抢执行权,结果中thread-0和main线程会交替打印输出。而当使用join()后,程序会等t线程执行结束后,再去执行main线程的内容。

 

interrupt:Thread的interrupt方法是设置线程的中断标记。

当线程调用sleep,wait,join方法时,线程处于阻塞状态。当对处于阻塞状态的线程调用interrupt方法时会抛出InterruptException异常,而这个异常会清除中断标记。

public class Demo {
    public static class Demo2 extends Thread{
        @Override
        public void run() {
            while (true){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    break;
                }
            }
        }
    }


    public static void main(String[] args) {
        Demo2 d = new Demo2();
        d.start();

        try {
            Thread.sleep(3_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        d.interrupt();
    }
}

如上代码中启动一个d线程,run方法中调用sleep方法让它每次执行都是阻塞状态。

让主线程休眠3秒后,调用d线程的interrupt方法,此时sleep方法会捕获到InterruptException异常。

3.线程池:

线程池的作用是管理线程资源,可以减少系统频繁的创建和销毁线程所消耗的时间。而当系统中创建了太多的线程在同时运行时,会给系统带来很大的负担。线程池可以解决这些问题。

线程池的概念是Executor这个接口,具体实现为ThreadPoolExecutor类。此类中包括4个构造函数:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), handler);
}
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.acc = System.getSecurityManager() == null ?
            null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

int corePoolSize:该线程池中最大的核心线程数

核心线程:

线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程,如果超过corePoolSize,则新建的是非核心线程

核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态)。

如果指定ThreadPoolExecutor的allowCoreThreadTimeOut这个属性为true,那么核心线程如果不干活(闲置状态)的话,超过一定时间(时长下面参数决定),就会被销毁掉

int maximumPoolSize:线程总数的最大数(核心线程+非核心线程)

long keepAliveTime:该线程池中非核心线程闲置超时时长

一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉

如果设置allowCoreThreadTimeOut = true,则会作用于核心线程

TimeUnit unit:有7种取值,在TimeUnit类中有7种静态属性

TimeUnit.DAYS;              天
TimeUnit.HOURS;             小时
TimeUnit.MINUTES;           分钟
TimeUnit.SECONDS;           秒
TimeUnit.MILLISECONDS;      毫秒
TimeUnit.MICROSECONDS;      微妙
TimeUnit.NANOSECONDS;       纳秒

BlockingQueue<Runnable> workQueue:一个阻塞队列,用来存储等待执行的任务

常用的workQueue类型:

  1. SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现<线程数达到了maximumPoolSize而不能新建线程>的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大

  2. LinkedBlockingQueue:是一个由链表实现的有界队列阻塞队列,但大小默认值为Integer.MAX_VALUE。这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize

  3. ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误

  4. DelayQueue:队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务

ThreadPoolExecutor的策略

上面介绍参数的时候其实已经说到了ThreadPoolExecutor执行的策略,这里给总结一下,当一个任务被添加进线程池时:

  1. 线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务
  2. 线程数量达到了corePoolsSize,则将任务移入队列等待
  3. 队列已满,新建线程(非核心线程)执行任务
  4. 队列已满,总线程数又达到了maximumPoolSize,就会由RejectedExecutionHandler抛出异常

常用的四种线程池:

如果不想手写一个线程池,可以用Executors提供的四种线程池,应用在一些常用的场景中。

CachedThreadPool()

可缓存线程池:

  1. 线程数无限制
  2. 有空闲线程则复用空闲线程,若无空闲线程则新建线程
  3. 一定程序减少频繁创建/销毁线程,减少系统开销

创建方法:

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

代码:
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

FixedThreadPool()

定长线程池:

  1. 可控制线程最大并发数(同时执行的线程数)
  2. 超出的线程会在队列中等待

创建方法:

参数int nThreads就是最大线程数,即maximumPoolSize

ExecutorService es = Executors.newFixedThreadPool(int nThreads);

代码:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

ScheduledThreadPool()

定长线程池:

  1. 支持定时及周期性任务执行。

创建方法:

//nThreads => 最大线程数即maximumPoolSize
ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(int corePoolSize);

代码:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

//ScheduledThreadPoolExecutor():
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue());
}

SingleThreadExecutor()

单线程化的线程池:

  1. 有且仅有一个工作线程执行任务
  2. 所有任务按照指定顺序执行,即遵循队列的入队出队规则

创建方法:

ExecutorService singleThreadPool = Executors.newSingleThreadPool();

代码:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

Callable()和Runnable()

上面介绍完了几种线程池的创建,那么接下来我们要给线程池中的线程添加任务。

而在接口ExecutorService中,提供了四个添加任务的方法:

public void execute(Runnable command)

<T> Future<T> submit(Callable<T> task);

<T> Future<T> submit(Runnable task, T result);

Future<?> submit(Runnable task);

看方法可以得出,如果使用execute(Runnable command)方法,无法获取到返回值。而在一些需要获取返回结果的场景中,就需要调用submit方法从而获取线程执行完返回的结果。 

参数Callable和Runnable:

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

如上代码,这两个接口都被@FunctionInterface标记,即JDK8的新特性:函数式接口,即是指仅仅只包含一个抽象方法的接口。

(1)Callable规定的方法是call(),Runnable规定的方法是run()。其中Runnable可以提交给Thread来包装下,直接启动一个线程来执行,而Callable则一般都是提交给ExecuteService来执行。
(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得
(3)call方法可以抛出异常,run方法不可以
(4)运行Callable任务可以拿到一个Future对象

ExecutorService es = Executors.newFixedThreadPool(2);
Future<?> hahaha = es.submit(new Runnable() {
    @Override
    public void run() {
        System.out.println("hahaha");//打印输出
    }
});
System.out.println(hahaha.get());//null

Future<String> submit = es.submit(new Callable<String>() {
    @Override
    public String call() throws Exception {
        return "xixixi";
    }
});
System.out.println(submit.get());//打印输出 xixixi

Future:

public interface Future<V> {

如果任务还没开始,执行cancel(...)方法将返回false;如果任务已经启动,执行cancel(true)方法将以中断执行此任务线程的方式来试图停止任务,如果停止成功,返回true;当任务已经启动,执行cancel(false)方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成),此时返回false;当任务已经完成,执行cancel(...)方法将返回false。mayInterruptRunning参数表示是否中断执行中的线程。

boolean cancel(boolean mayInterruptIfRunning);


如果任务完成前被取消,则返回true。

boolean isCancelled();


如果任务执行结束,无论是正常结束或是中途取消还是发生异常,都返回true。

boolean isDone();


获取异步执行的结果,如果没有结果,此方法会阻塞直到异步计算完成。

V get() throws InterruptedException, ExecutionException;


获取异步执行结果,如果没有结果可用,此方法会阻塞,但是会有时间限制,如果阻塞时间超过设定的timeout时间,该方法将抛出异常。

V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;

}

通过方法分析可以得出Future提供了三种功能:

  1. 终端执行中的任务
  2. 判断任务是否完成
  3. 获取任务执行完成的结果

线程池的关闭:

 

线程池的关闭

  ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:

  • shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
  • shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值