Java初学者笔记-10、多线程线程池
线程(Thread)是一个程序内部的一条执行流程。
程序中如果只有一条执行流程,那这个程序就是单线程的程序。
多线程是指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行)。
创建线程
方式一:继承Thread类
使用步骤
- 继承Thread类重写run()。
- 创建对象。
- 启动start()。
main方法本身是由一条主线程运行的。
优点:编码简单。
缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展。
注意事项
- 启动线程必须是调用start方法,不是调用run方法。
- 直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。
- 只有调用start方法才是启动一个新的线程执行。
- start()是向CPU注册,然后执行run()。
- 不要把主线程任务放在启动子线程之前。
方式二:实现Runnable接口
使用步骤
- 实现Runnable接口重写run()。
- 创建对象。
- 交给Thread对象。
- 启动start()。
Runnable r = new MyRunnable(); new Thread(r).start(); //=========使用匿名内部类简化========== new Thread(new Runnable(){run(){}}).start(); //=============继续简化============== new Thread( ()->{ "1".sout; } ).start();
优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。
缺点:需要多一个Runnable对象。
方式三:实现Callable接口
前两种创建方式的问题:假如线程执行完毕后有一些数据需要返回,他们重写的run方法均不能直接返回结果。因为重写的run()方法的返回值是void。
jdk5之后提供了Callable接口和FutureTask类来实现。即方式三。
最大的优点:可返回线程执行完毕后的结果。
使用步骤
- 实现Callable接口重写
call()
方法。 - 创建对象交给FutureTask(线程任务对象,取结果)。
- 把FutureTask对象交给Thread对象。
- 启动start()。
- 线程执行完毕后,通过FutureTask对象的
get()
方法获取线程任务执行的结果。
注意:FutureTask本质是一个Runnable线程任务对象。
class CallableThread implements Callable<Integer> { @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i < 100; i++) { if (i % 2 == 0){ sum += i; } } return sum; } }
CallableThread callableThread = new CallableThread(); FutureTask<Integer> futureTask = new FutureTask<>(callableThread); new Thread(futureTask, "name").start(); try { Integer sum = futureTask.get(); System.out.println("计算 sum = " + sum); } catch (Exception e) { e.printStackTrace(); }
总结:先实现Callable接口。创建对象并传给FutureTask对象,FutureTask对象传给Thread对象,Thread对象启动start()。
线程的常用方法
Thread提供的常用方法 | 说明 |
---|---|
public void run() | 线程的任务方法 |
public void start) | 启动线程 |
public String getName() | 获取当前线程的名称,线程名称默认是Thread-索引 |
public void setName(String name) | 为线程设置名称 |
public static Thread currentThread () | 获取当前执行的线程对象 |
public static void sleep(long time) | 让当前执行的线程休眠多少毫秒后,再继续执行 |
public final void join()... | 让调用当前这个方法的线程先执行完! |
Thread提供的常见构造器 | 说明 |
---|---|
public Thread (String name) | 可以为当前线程指定名称 |
public Thread (Runnable target) | 封装Runnable对象成为线程对象 |
public Thread(Runnable target, String name) | 封装Runnable对象成为线程对象,并指定线程名称 |
线程安全与线程同步
多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题。
银行的线程安全问题。存在修改资源的时候才会出现线程安全问题。
线程同步:线程安全问题的解决方案。
线程同步的核心思想:让多个线程先后依次访问共享资源,这样就可以避免出现线程安全问题。
加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来。
加锁方式一:同步代码块
作用:把访问共享资源的核心代码给上锁,以此保证线程安全。
原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行。
同步锁的注意事项:对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug。
使用步骤:
先找核心代码,ctrl+alt+T,选9,在括号里填一个对于线程来说唯一的对象。对于实例方法,使用this作为锁对象,对于静态方法使用字节码(如:Account.class)文件作为锁对象。
加锁方式二:同步方法
作用:把访问共享资源的核心方法给上锁,以此保证线程安全。
相比于方式一,锁的范围更整体一些,即直接把整个方法锁上。
理论上来说,同步代码块好,只锁核心代码。但同步方法可读性好。
使用步骤:
只需要在方法加上synchronized
关键字。
加锁方式三:Lock锁
Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。
使用步骤:
Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock
来构建Lock锁对象。
ReentrantLock
对象提供的方法有lock()
和unlock()
。
使用细节:
- 给锁对象用final关键字进行保护。
public class Account { private String cardId; // 卡号 private double money; // 余额 private final Lock lk = new ReentrantLock(); ... }
- 将
unlock()
放到finally里面。IDEA中ctrl+alt+T,选第七个。
public void drawMoney(double money) { String name = Thread.currentThread().getName(); lk.Lock();// 上锁 try { //判断余额是否足够 if(this.money >= money){ // 余额足够,取钱 system.out.println(name + "取钱成功,吐出了" + money +"元"); // 更新余额 this.money -= money; System.out.println(name +"取钱成功,取钱后,余额剩余"+ this.money +"元"); } else { // 余额不足 System.out.println(name + "取钱失败,余额不足"); } } finally { lk.unlock();// 解锁 }
线程池(使用)
线程池就是一个可以复用线程的技术。
不使用线程池的问题:
用户每发起一个请求,后台就需要创建一个新线程来处理,下次新任务来了肯定又要创建新线程处理的,创建新线程的开销是很大的,并且请求过多时,肯定会产生大量的线程出来,这样会严重影响系统的性能。
线程池的组成
线程池由工作线程(WorkThread)和任务队列(WorkQueue)组成。
任务队列只能放Runnable线程和Callable线程。
创建线程池
JDK 5.0起提供了代表线程池的接口:ExecutorService
。
对应的实现类是ThreadPoolExecutor
。
方式一:通过 ThreadPoolExecutor 创建线程池
创建方法
public ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue <Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
- 参数一:corePoolSize:指定线程池的核心线程的数量。(正式工)
- 参数二:maximumPoolSize:指定线程池的最大线程数量。(最大员工数)
- 参数三:keepAliveTime:指定临时线程的存活时间。 (临时工多久被开除)
- 参数四:unit:指定临时线程存活的时间单位(秒、分、时、天)。
- 参数五:workQueue:指定线程池的任务队列。(客人排队的地方)
- 参数六:threadFactory:指定线程池的线程工厂。(HR)
- 参数七:handler:指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新任务来了该怎么处理)
ExecutorService pool = new ThreadPoolExecutor (3, 5, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory), new ThreadPoolExecutor.AbortPolicy());
ExecutorService 的常用方法
方法名称 | 说明 |
---|---|
void execute (Runnable command) |
执行 Runnable 任务 |
Future<T> submit(Callable<T> task) |
执行 Callable任务,返回未来任务对象,用于获取线程返回的结果 |
void shutdown() |
等全部任务执行完毕后,再关闭线程池! |
List <Runnable> shutdownNow() |
立刻关闭线程池,停止正在执行的任务,并返回队列中来执行的任务 |
一般不关闭线程池!
线程池的注意事项
- 什么时候开始创建临时线程?
- 新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
- 什么时候会拒绝新任务?
- 核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务。
- 任务队列装的是还未处理的任务。
任务拒绝策略
策略 | 说明 |
---|---|
ThreadPoolExecutor.AbortPolicy() | 丢弃任务并抛出RejectedExeCutionException异常。是默认的策略 |
ThreadPoolExecutor. DiscardPolicy() | 丢弃任务,但是不抛出异常,这是不推荐的做法 |
ThreadPoolExecutor. DiscardOldestPolicy() | 抛弃队列中等待最久的任务 然后把当前任务加入队列中 |
ThreadPoolExecutor. CallerRunsPolicy() | 由主线程负责调用任务的run()方法从而绕过线程池直接执行 |
方式二:通过Executors创建线程池(不推荐)
是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象。
注意:大型并发系统环境中使用Executors如果不注意可能会出现系统风险。阿里的Java开发手册里明确规定禁止通过Executors创建线程池。
方法名称 | 说明 |
---|---|
public static ExecutorService newFixedThreadPool(int nThreads) | 创建固定线程致量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程代替它。 |
public static ExecutorService newSingleThreadExecutor() | 创建只有一个线程的线程池对象,如果该线程出現异常而结束,那么线程池会补充一个新线程。 |
public static ExecutorService newCachedThreadPool() | 线程數量随着任务增加而增加,如果线星任务执行完毕且空闲了60s则会被回收掉。 |
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) | 创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。 |
本文作者:subeipo
本文链接:https://www.cnblogs.com/subeipo/p/18685139/java-chu-xue-zhe-bi-ji10-duo-xian-cheng-xian-cheng
版权声明:本作品采用署名—非商业性使用—相同方式共享 4.0 协议许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步