线程的创建方式

Thread

我们通过 new 一个 Thread 的子类,并调用其 start 方法来启动一个线程

public class MyThread extends Thread {
    @Override
    public void run() {
        ...
    }
}

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

问题:为什么调用的是 start 方法而不是 run 方法

1、当 new 一个 Thread 后,线程就进入了新建状态

2、这时调用 start 会启动一个线程并使线程进入了就绪状态,当分配到时间片后线程就可以开始运行了。 start 会执行线程的相应准备工作,然后自动执行 run 方法

3、但如果直接执行 run 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作

public synchronized void start() {
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    
    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {

        }
    }
}

它是一个同步方法,它会去调用 start0 方法,该方法是一个 native 方法,通过该方法会让虚拟机帮我们运行线程

Runnable

在 Thread 中,有两个构造方法

public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

我们可以传入一个 Runnable 接口来创建线程

public class MyRunnableThread implements Runnable {
    @Override
    public void run() { 
    	...
    }
}
public static void main(String[] args) {
    new Thread(new MyRunnableThread()).start();
}

使用 匿名内部类Lambda 表达式简化:

public static void main(String[] args) {
    // 优化1
    new Thread(new Runnable() {
        @Override
        public void run() {
            ...
        }
    }).start();
	    
    // 优化2
    new Thread(() -> {
        ...
    }).start();
}

使用该方式创建线程的优点:接口灵活方便,可以使一个对象被多个线程共享

底层原理:静态代理模式

Callable

Callable 是 J.U.C 引入的一个创建线程的方式

问题:Callable 与 Runnable 的区别

1)Callable 可以通过 call 获得返回值,Runnable 需要借助共享变量获取

2)call 可以抛出异常,而 Runnable 只能通过 setDefaultUncaughtExceptionHandler() 的方式才能在主线程中捕捉到子线程异常


在 Runnable 中的体系中,Runnable 有一个子类接口:RunnableFuture

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

而 RunnableFuture 有一个实现类:

public class FutureTask<V> implements RunnableFuture<V> {
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }
    ...
}

在该类中,可以传入一个 Callable 接口来创建一个对象,这个对象可以作为 Runnable 接口的实现传入 Thread 借此创建线程

简单的说就是:通过 FutureTask 让 Callable 和 Runnable 产生联系

创建一个 Callable 接口,显示定义返回值类型,并覆写 call,抛出异常

实现 Callable 接口,设置返回值类型,覆写 call 方法并抛出异常

class MyCallableThread implemets Callable<Boolean> {
    @Override
    public Integer call() throws Exception {
        return 1024;
    }
}

public static void main(String[] args) {
	FutureTask<Integer> future = new FutureTask<>(new MyCallableThread());
    new Thread(future).start();
    
    // 获取返回结果
    Integer o = future.get(); 
}

线程池

通过线程池,我们提前创建多个线程放在其中,在时候的时候直接从池中获取,用完放回池中,可以在并发下节省很多时间

使用线程池的优点:

  1. 提高响应速度,减少了创建新线程的时间
  2. 降低资源消耗,重复利用线程池中线程
  3. 便于线程管理

线程池的主要作用:

  1. 进行线程的复用
  2. 控制最大并发数
  3. 进行线程的管理

线程池的创建

利用 Executors 工具类可以创建线程池(不推荐)

    Executors.newSingleThreadExecutor();  // 单实例线程
    Executors.newFixedThreadPool(5);	  // 创建一个固定大小的线程池
    Executors.newCachedThreadPool(); 	  // 可伸缩的
    ...

在 J.U.C 中,有一个 Executor 接口,它类似于 Collection,其有一个子类接口:ExecutorService,创建的线程池的类型即为 ExecutorService

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

    ExecutorService threadPool = Executors.newSingleThreadExecutor();

    // 使用线程池中的线程
    threadPool.execute(() -> {
        System.out.println(Thread.currentThread().getName());
    });

    // 关闭线程池
    threadPool.shutdown();
}

自定义线程池

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

在使用 Executors 工具类创建线程池,都是利用 new ThreadPoolExecutor 来创建线程池

一般情况下,我们都使用该方式自定义线程池:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

七大参数说明:

参数说明
corePoolSize核心线程数量
maximumPoolSize最大线程数
keepAliveTime空闲线程存活时间
TimeUnit unit时间单位
BlockingQueue workQueue阻塞队列
ThreadFactory threadFactory线程工厂,用来创建线程
RejectedExecutionHandler handle拒绝策略,阻塞队列满时的拒绝方式

演示说明:

public static void main(String[] args) throws InterruptedException {
    ExecutorService threadPool = new ThreadPoolExecutor(
        2, 
        5,
        3,
        TimeUnit.SECONDS,
        new LinkedBlockingDeque<>(3), // 需要设置阻塞队列大小
        Executors.defaultThreadFactory(), // 默认g
        new ThreadPoolExecutor.AbortPolicy // 拒绝策略,抛出异常
    );

    // 最大承载:阻塞队列 + max 的值,超过最大承载则使用拒绝策略
    for (int i = 0; i < 9; i++) {
        threadPool.execute(() -> {
            System.out.println(Thread.currentThread().getName() + "ok");
        });
    }
    threadPool.shutdown();
}

拒绝策略

在自定义线程池的时候,设置了线程池的最大线程数,以及一个固定大小的阻塞队列

当线程池所有线程都在工作,并且阻塞队列也满了的时候,如果还有线程需要开启,则需要使用拒绝策略来拒绝线程池的访问

四种拒绝策略:

  • AbortPolicy:抛出异常 RejectedExecutionException
  • CallerRunsPolicy:将任务退回到调用者
  • DiscardPolicy:丢弃无法处理的任务,不予任何处理也不抛出异常
  • DiscardOldestPolicy:丢弃队列中等待最久的任务,把当前任务加入队列中尝试再次提交当前任务

最大线程数的设定

问题:应该如何自定义线程池中的最大线程数

根据任务的类型进行确认

  • CPU 密集型:CPU 核数 + 1
  • IO 密集型:2 * CPU 核数
Runtime().getRuntime().availableProcessors(); // 获取 CPU 核数
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值