赞
踩
在java开发中,当需求复杂度和并发量增大时,引入多线程来充分发挥机器的性能是很有必要的,但是引入多线程以后系统复杂度随之上升,使用不当带来OOM或者CPU疯狂飙升导致服务崩溃的风险增大,这时候理解多线程的使用变得尤为重要。下文谈谈我自身开发中,对java中使用多线程的理解。不过什么是线程,线程和进程的关系本文不再赘述。
在java语言基础库中,提供了一个Thread类,用来表示或操作线程,Thread类可以视为是Java标准库提供的API,Java是支持多线程编程的,在Java中创建好的Thread实例,其实就表示操作系统中存在的一个线程,是一一对应的关系,操作系统提供了一组关于线程的API(C语言),Java对于这组API进一步封装之后,把API变成适用java语言的各个方法,存在Thread类中。
查看Thread类的原码,我们可以看到几乎都是native方法,意味着底层是操作系统提供的c语言库函数
public static native Thread currentThread();
public static native void yield();
public static native void sleep(long millis) throws InterruptedException;
1)那么如何快速创建一个线程用来异步执行一些业务呢?
或许你也曾疑惑或正在疑惑,很多人说创建线程有3种方法,有的人说是4种,那么到底有几种?如何合理选择创建线程的方式
我们知道在Java中Thread和操作系统的线程一一对应,而JAVA是面向对象的编程语言,万物皆对象,线程肯定也是一个对象,我们能不能通过new一个Thread的实例来创建线程呢,如果你从这个思路来考虑,那么你已经和JAVA之父的思路在同一个频道上了,也就是说是可以这么来创建线程的
public static void main(String[] args) {
Thread thread = new Thread();
// 注意,在Thread中有start()和run()两个方法十分相似,但却天差地别
// 但只有调用start()方法才表示该线程进入就绪状态,等待操作系统运行,线程的状态大家可以自行了解下
// 也就是说线程具体什么时候运行需要等待操作系统调用
thread.start();
}
但是你运行完会发现和没执行一样什么都没发生,为什么,这是因为我们没有让这个线程做事
// 创建一个类,继承Thread,重写run方法适配自己的业务逻辑
class MyThread1 extends Thread {
@Override
public void run() {
System.out.println("这是第一种创建线程的方法,执行线程名:"+Thread.currentThread().getName());
}
}
// 实例化一个对象并执行,这时控制台会打印出响应的执行信息
public static void main(String[] args) {
Thread thread = new MyThread1();
thread.start();
}
当然,实际上继承Thread类只是其中一种创建线程的方式,我们来看看其他方案
a: 实现Runnable接口
// 实现Runnable接口,将业务代码写在run方法内
static class MyThread2 implements Runnable {
@Override
public void run() {
System.out.println("这是第二种创建线程的方法,执行线程名:"+Thread.currentThread().getName());
}
}
// 实例化一个对象,将对象作为target传入Thread类,利用Thread的start方法将线程提交给cpu调度
public static void main(String[] args) {
Thread thread = new Thread(new MyThread2());
thread.start();
}
b: 通过Callable和Future接口创建线程
// 实现Callable接口,配合FutureTask创建线程 public static void main(String[] args) { FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() { @Override public Integer call() throws ExecutionException { System.out.println("这是第3种创建线程的方法,执行线程名:"+Thread.currentThread().getName()); return 1; } }); // 同样需要将target传给Thread Thread thread = new Thread(task); thread.start(); try { // 此方式创建线程可以拿到执行的返回值,并且业务方法报错时,可以捕获到 // 需要注意的是调用get方法会阻塞主线程 Integer integer = task.get(); System.out.println(integer); } catch (InterruptedException interruptedException) { interruptedException.printStackTrace(); System.out.println("执行中断"); } catch (ExecutionException e) { e.printStackTrace(); System.out.println("业务异常"); } }
归纳:由此可见,不管使用哪一种方式,最后都要通过Thread类来维护,笔者认为实际创建线程的方式就一种,那就是new一个Thread对象。不过实现Runnable带来的好处是可以继承别的父类,以便于扩展,实现Callable,配合FutureTask可以实现获取执行的返回值和捕获执行异常的场景。
在上文我们知道了创建线程的各种方式,那么为什么还要引入线程池的概念呢。
对比一下数据库连接池,作用无非是复用已有的连接,避免每次请求都重新建立连接,从而减少建立和断开连接的开销,提升数据库操作的响应速度和稳定性,还可以监控连接情况等。线程池其实也是差不多的作用。
线程池相关的类基本都在java.util.concurrent包下,翻阅原码可以看到是Doug Lea这个人一手编写,太牛了。
本文我们着重谈谈java.util.concurrent.ThreadPoolExecutor,因为只要你实例化这个对象出来,就已经创建好了一个线程池,剩下的就是按照业务需要丢线程进去执行
1)、如何创建线程池
通过介绍我们知道new ThreadPoolExecutor即可创建,但是看源码得知构造方法有很多,我们具体用哪个更合适呢
corePoolSize 核心线程池大小 - 表示该线程池有多少个一直可以循环使用不会被销毁的线程资源
maximumPoolSize 最大线程池大小 - 表示该线程池的线程总数,当核心线程数慢时,再有线程被添加时会进入对列等待,队列满时,线程池最大会创建(maximumPoolSize - corePoolSize)个非核心线程来执行新添加的线程
keepAliveTime 非核心线程空闲多久会被释放
unit 超时时间单位
workQueue 阻塞队列 - 核心线程满时存放线程的队列
threadFactory 线程工厂 - 可以通过自定义工厂来定义线程名等
defaultHandler 拒绝策略 - 当线程池被占满时通过什么策略拒绝
2)、通过对构造方法的7个参数的理解,创建适合的线程池来使用
public static void main(String[] args) {
// 自定义线程工厂,自定义线程名
ThreadFactory factory = new ThreadFactoryBuilder().setNameFormat("my-thread-%d").build();
ThreadPoolExecutor pool = new ThreadPoolExecutor(6, 10, 10,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
factory,
new ThreadPoolExecutor.AbortPolicy());
// 将上文中实现Runnable接口的一个对象丢进线程池,由线程池来管理和调度
// 也可以调用pool.submit()传入Callable的实现类
pool.execute(new MyThread2());
// 注意,线程池被创建时一般不会主动关闭,需要手动shutdown()
// 一般我们会将线程池创建在一个常量类中,当一个静态常量来使用
pool.shutdown();
}
3、通过使用现有的api创建线程池(不推荐)
// 比如
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new MyThread1());
注意到此方式创建的线程池是ExecutorService类型,为什么呢,有兴趣的同学可以看看ThreadPoolExecutor和ExecutorService的关系,有兴趣的朋友可以自己研究下
注:笔者想分享的已经结束了,本文如有不正确之处,欢迎大家批评纠正
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。