目前为止我们大部分程序都是单线程——都只有一条顺序执行流,程序从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();
}
}
}