JAVA多线程编程(一)

58 阅读3分钟

一、线程状态

省略

二、实现方式

2.1继承Thread

public class MyThread extends Thread{

    public MyThread(String name){
        this.setName(name);
    }

    @Override
    public void run() {
        System.out.println("该方法用于自定义线程执行的操作");
    }

    public static void main(String[] args) {
        new MyThread("mine").start();
    }
}

2.2实现Runnable

public class MyRunnable implements Runnable{
    @Override
    public void run() {

    }

    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        new Thread(myRunnable,"myRunnable").start();
    }
}

2.3实现Callable,并使用FutureTask包装

public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return 123;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable myCallable = new MyCallable();
        FutureTask<Integer> ft = new FutureTask<>(myCallable);
        Thread thread = new Thread(ft);
        thread.start();
        System.out.println(ft.get());
    }
}

三、Runnable、Callable、Future接口

类图关系如下

image.png

我们知道,构建Thread可以通过传入Runnable,但是通过观察类图,我们可以发现Callable接口并没有实现Runable,所以需要借助FutureTask进行包装才能使用。FutureTask实现了RunnableFuture接口,顾名思义,既实现了Runnable,又实现了Future。

三、线程池

3.1线程池的使用

Java提供了ThreadPoolExecutor类,要想构建一个线程池,一共提供了四个构造方法,涉及七个核心参数。

public class ExecutorsDemo {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
        for (int i = 0; i < 10; i++) {
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "hello");
                }
            });
        }
    }
}

3.2线程池的核心参数

  • 核心线程数:默认一直存在于线程池中,而非核心线程数量长时间闲置会销毁

  • 最大线程数:核心线程数+非核心线程数量

  • 存活时间:非核心线程闲置超时时长

  • 存活时间的单位

  • 阻塞队列:维护着等待执行的Runnable任务对象,常用阻塞队列如下

    • LinkedBlockingQueue:基于链表的阻塞队列,默认大小是Integer.MAX_VALUE
    • ArrayBlockingQueue:基于数组的阻塞队列,需要指定大小
    • SynchronousQueue:内部容量为0,每个put操作必须等待一个take操作
    • DelayQueue:延迟队列,该队列中的元素只有当其指定的延迟时间到,才能够从队列中获取到该元素
  • 线程工厂

  • 拒绝策略:线程数量大于最大线程数时的处理策略,总共有四种

    • ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出异常
    • ThreadPoolExecutor.DiscardPolicy:丢弃新来的任务,不抛出异常
    • ThreadPoolExecutor.DiscardOldestPolicy:抛弃队列头部的任务,重试
    • ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

3.3线程池的工作流程

通过上面的核心参数,我们大概也能猜到线程池的工作流程,其实很简单,就是两个阈值与一个队列。

  • 当线程数小于corePoolSize时,创建新线程
  • 当线程数大于>=corePoolSize,后续的线程任务会自动加入阻塞队列
  • 当阻塞队列满了,就会创建非核心线程
  • 当阻塞队列满了,并且线程数达到了maximumPoolSize,就会执行拒绝策略

image.png

3.4常见线程池

Java提供了Executors类,它有4个静态方法,方便我们平时构建线程池。

  • newCachedThreadPool
  • newFixedThreadPool
  • newSingleThreadPool
  • newScheduledThreadPool

其实这些静态方法就是根据设置核心参数的不同值,以达到不同的效果,这边大家有兴趣的话请自行查看代码,不再赘述。

3.5类图关系

image.png