01、多线程的概念
目前为止我们大部分程序都是单线程——都只有一条顺序执行流,程序从main方法开始执行,依次向下执行每条语句,如果程序执行某行代码时遇到了阻塞,则程序将会停滞在该处。实际情况是单线程的程序往往功能非常有限。多线程使得程序中的多个任务可以同时执行。java的重要功能之一就是内部支持多线程。
程序、进程、线程的概念
程序:一个静止的app
进程:一个运行的app,在内存里会出现一条进程
线程:一个进程可能包含多个可以同时运行的任务。线程是指一个任务从头到尾的执行流程。
多线程的优点
并发编程方便进行业务拆分,提升系统的并发能力。
线程提供了一个任务的运行机制。对于java而言,可以在一个程序中并发的启动多个线程。这些线程可以在多处理器上同时运行(并行)。在单处理器系统中,多个线程共享CPU时间称为时间分享,而操作系统负责调度并分配资源给它们(并发)。
多线程可以使程序反应更快、交互性更强、执行效率更高。在一些情况下,即使在单处理器系统上,多线程程序的运行速度也比单线程程序更快。Java对多线程程序的创建和运行,以及锁定资源以避免冲突提供了非常好的支持。
可以在程序中创建附加的线程以执行并发任务。在java中,每个任务都是Runnable接口的一个实例,也称为可运行对象。线程本质上来讲就是便于任务执行的对象。
java中实现多线程的方式
java中实现多线程的方式一:实现Runnable接口
任务需要实现Runnable,任务必须从线程开始执行
package edu.uestc.avatar; public class TaskThreadDemo { public static void main(String[] args) { //创建任务 Runnable printA = new PrintLetter('A', 100); Runnable printB = new PrintLetter('B', 100); Runnable printNumber = new PrintNumber(100); //任务必须从线程开始运行 Thread t1 = new Thread(printA); Thread t2 = new Thread(printB); Thread t3 = new Thread(printNumber); //启动线程 t1.start(); t2.start(); t3.start(); } } /** * 任务需要实现Runnable,任务必须从线程开始执行 * 该任务完成打印特定字母times次 * */ class PrintLetter implements Runnable{ private char letter; private int times; public PrintLetter(char letter, int times) { this.letter = letter; this.times = times; } /** * 指明如何完成这个任务 */ @Override public void run() { for(int i = 0; i < times; i++) System.out.print(letter); } } /** * 该任务连续打印数字 * */ class PrintNumber implements Runnable{ private int lastNumber; public PrintNumber(int lastNumber) { this.lastNumber = lastNumber; } @Override public void run() { for(int i = 1; i <= lastNumber; i++) System.out.print(i + " "); } }
java中实现多线程的方式二:继承Thread类
由于Thread类实现了Runnable接口,所以可以定义一个类扩展自Thread类,并且覆盖run方法。
package edu.uestc.avatar; public class CustomThreadClient { public static void main(String[] args) { //创建线程 Thread custom = new CustomThread(); //启动线程 custom.start(); } } /** * 由于Thread类实现了Runnable接口, * 所以可以定义一个类扩展自Thread类,并且覆盖run方法。 * */ class CustomThread extends Thread{ private int ticket = 100; @Override public void run() { while(ticket > 0) { System.out.println(Thread.currentThread().getName() + "正在卖出编号为" + ticket + "的票"); ticket--; } } }
但是,不推荐使用这种方法,因为它将任务和运行任务的机制混在了一起。将任务从线程中分离出来是比较好的设计。同时,Java存在单继承的限制。
java中实现多线程的方式三:线程池
使用方法一或方法二对单一任务的执行是很方便的,但是由于必须为每一个任务创建一个线程。因此对大量任务而言是不够高效的。为每一个任务开始一个新线程可能会限制吞吐量并且造成性能降低。线程池是管理并发执行任务个数的理想方法。Java提供Executor接口来执行线程池中的任务,提供ExecutorService接口来管理和控制任务。ExecutorService是Executor子接口。
为了创建一个Executor对象,可以使用Executors类中的静态方法
- newFixedThreadPool(int):在线程池中创建可重用的固定数目的线程。如果线程完成了任务的执行,它可以被重新使用以执行另外一个任务。如果线程池中所有的线程都不是处于空闲状态,而且有任务在等待执行,那么在关闭之前,如果由于一个错误终止了一个线程,就会创建一个新线程来替换它
- newCachedThreadPool():如果线程池中所有的线程都不是处于空闲状态,而且有任务在等待执行,会创建一个新线程。如果缓冲池中的线程在60秒内都没有被使用就该终止它。对许多小任务而言,一个缓冲池已经足够
- newSingleThreadExecutor():创建一个只有一个线程的线程池。
- newScheduledThreadPool(int corePoolSize):创建具有指定线程数的线程池,可以在指定延迟后执行任务。corePoolSize指池中保存的线程数,即使线程是空闲的也保存在池中
- newSingleThreadScheduledExecutor():创建一个只有一个线程的线程池,可以在指定延迟后执行任务
- newWorkStealingPool(int parallelism):创建持有足够的线程的线程池来支持给定的并行级别,该方法会使用多个队列来减少竞争。newWorkStealingPool()是为简化版本,以机器CPU数量作为参数传入。
package edu.uestc.avatar; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ExecutorDemo { public static void main(String[] args) { //ExecutorService executor = Executors.newFixedThreadPool(3); //顺序执行任务 ExecutorService executor = Executors.newFixedThreadPool(1); //提交任务给线程池 executor.execute(new PrintLetter('A', 100)); executor.execute(new PrintLetter('B', 100)); executor.execute(new PrintNumber(100)); //关闭执行器,允许执行器中的任务完成,一旦关闭,不再接收新的任务 executor.shutdown(); } }
创建线程方式四:Callable
Callable提供了一个call()方法可以作为线程执行体;比run方法功能更强大,
- 可以获得任务执行返回值;
- 通过与Future的结合,可以实现利用Future来跟踪异步计算的结果
Future接口代表Callable接口里call()方法的返回值,提供了FutureTask实现类,且实现了Runnable接口,可以使用FutureTask实例作为Thread类的target
- Future是一个接口,代表了一个异步计算的结果。接口中的方法用来检查计算是否完成、等待完成和得到计算的结果。
- 当计算完成后,只能通过get()方法得到结果,get方法会阻塞直到结果准备好了。
- 如果想取消,那么调用cancel()方法。其他方法用于确定任务是正常完成还是取消了。
- 一旦计算完成了,那么这个计算就不能被取消。
FutureTask类
- FutureTask类实现了RunnableFuture接口,而RunnnableFuture接口继承了Runnable和Future接口,所以说FutureTask是一个提供异步计算的结果的任务。
- FutureTask可以用来包装Callable或者Runnbale对象。因为FutureTask实现了Runnable接口,所以FutureTask也可以被提交给Executor
package edu.uestc.avatar; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class CallableAndFutureDemo { public static void main(String[] args) { //创建Callable对象,使用FutureTask来保证Callable对象 FutureTask<Integer> task = new FutureTask<Integer>(()->{ int i = 0; for(; i < 100; i++) { System.out.println(Thread.currentThread().getName() + "循环遍历i的值:" + i); } //call()方法有返回值 return i; }); for(int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + "循环遍历i的值:" + i); if(i == 20) { new Thread(task,"有返回值的线程").start(); } } //获取线程的返回值 try { System.out.println(task.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } }