一.线程创建的几种方式
1.继承Thread类
public class MyThread extends Thread {
public MyThread() {
}
public void run() {
for(int i=0;i<10;i++) {
System.out.println(Thread.currentThread()+":"+i);
}
}
public static void main(String[] args) {
MyThread mThread1=new MyThread();
MyThread mThread2=new MyThread();
MyThread myThread3=new MyThread();
mThread1.start();
mThread2.start();
myThread3.start();
}
}
2.实现Runnable接口,重写run()方法
public class MyThread implements Runnable{
public static int count=20;
public void run() {
while(count>0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-当前剩余票数:"+count--);
}
}
public static void main(String[] args) {
MyThread Thread1=new MyThread();
Thread mThread1=new Thread(Thread1,"线程1");
Thread mThread2=new Thread(Thread1,"线程2");
Thread mThread3=new Thread(Thread1,"线程3");
mThread1.start();
mThread2.start();
myThread3.start();
}
}
3.实现callable接口,重写call()方法(配合Future接口)
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MyThread implements Callable<String> {
private int count = 20;
@Override
public String call() throws Exception {
for (int i = count; i > 0; i--) {
// Thread.yield();
System.out.println(Thread.currentThread().getName()+"当前票数:" + i);
}
return "sale out";
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
Callable<String> callable =new MyThread();
//Future接口的实现类
FutureTask <String>futureTask=new FutureTask<>(callable);
Thread mThread=new Thread(futureTask);
Thread mThread2=new Thread(futureTask);
Thread mThread3=new Thread(futureTask);
mThread.start();
mThread2.start();
mThread3.start();
System.out.println(futureTask.get());
}
}
4.通过Executors工具类创建线程池,并启动线程
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.execute(()->{
try {
Thread.sleep(1000);
System.out.println("线程池启动线程");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("主线程运行结束");
}
二.Runnable接口和Callable接口的区别
1. Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;
2. Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执⾏行行的结果
四. Callable和Future关系(Callable使用需要Future和FutureTask配合)
1、Callable 和 Future 是比较有趣的一对组合。当我们需要获取线程的执行行结果时,就需要用到它们。Callable用于产生
结果,Future用于获取结果。
2、Callable接口使用泛型去定义它的返回类型。Executors类提供了了一些有用的方法去在线程池中执行行Callable内的任
务。由于Callable任务是并行的,必须等待它返回的结果。java.util.concurrent.Future对象解决了了这个问题。
3、在线程池提交Callable任务后返回了了一个Future对象,使用它可以知道Callable任务的状态和得到Callable返回的执行行
结果。Future提供了了get()方法,等待Callable结束并获取它的执行结果。
三.线程的五个状态(五种状态,创建、就绪、运行、阻塞和死亡)
线程通常都有五种状态,创建、就绪、运行、阻塞和死亡。
i. 第一是创建状态。在生成线程对象,并没有调用该对象的start⽅方法,这是线程处于创建状态。
ii. 第二是就绪状态。当调用了线程对象的start方法之后,该线程就进⼊入了了就绪状态,但是此时线程调度程序还没有
把该线程设置为当前线程,此时处于就绪状态。在线程运行行之后,从等待或者睡眠中回来之后,也会处于就绪状
态。
iii. 第三是运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运⾏行行
run函数当中的代码。
iv. 第四是阻塞状态。线程正在运行的时候,被暂停,通常是为了了等待某个时间的发生(比如说某项资源就绪)之后再
继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。
v. 第五是死亡状态。如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线
程,无法再使用start方法令其进入就绪。
四.线程池创建方式
Java通过Executors框架提供了四种创建线程池,分别为:
1、newCachedThreadPool创建一个可缓存线程池,如果线程使用数超过处理需要,可灵活回收空闲线程,若无可
回收,则新建线程。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
2、newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列列中等待。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
3、newScheduledThreadPool 创建一个定长线程池,⽀支持定时及周期性任务执⾏行行。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
4、newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务
按照指定顺序(FIFO, LIFO, 优先级)执行行。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
实际开发过程中,我们创建线程池并没有使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样
的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明: Executors 返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool :
允许的请求队列长度为 Integer.MAX_VALUE ,可能会堆积大量的请求,从而导致 OOM 。
2) CachedThreadPool 和 ScheduledThreadPool :
允许的创建线程数量为 Integer.MAX_VALUE ,可能会创建大量的线程,从而导致 OOM 。
五. Executor, ExecutorService 和 Executors 间的区别与联系
- Executor 和 ExecutorService 这两个接口主要的区别是:ExecutorService 接口继承了 Executor 接口,是 Executor 的子接口
- Executor 和 ExecutorService 第二个区别是:Executor 接口定义了
execute()
方法用来接收一个Runnable
接口的对象,而 ExecutorService 接口中的submit()
方法可以接受Runnable
和Callable
接口的对象。 - Executor 和 ExecutorService 接口第三个区别是 Executor 中的
execute()
方法不返回任何结果,而 ExecutorService 中的submit()
方法可以通过一个 Future 对象返回运算结果。 - Executor 和 ExecutorService 接口第四个区别是除了允许客户端提交一个任务,ExecutorService 还提供用来控制线程池的方法。比如:调用
shutDown()
方法终止线程池。 - Executors 类提供工厂方法用来创建不同类型的线程池。比如:
newSingleThreadExecutor()
创建一个只有一个线程的线程池,newFixedThreadPool(int numOfThreads)
来创建固定线程数的线程池,newCachedThreadPool()
可以根据需要创建新的线程,但如果已有线程是空闲的会重用已有线程。
六.线程池创建时各个参数的解释及线程池的执行流程
new ThreadPoolExecutor()参数说明(Executors创建线程池底层还是调用这个)
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime,TimeUnit unit,BlockingQueue workQueue,
ThreadFactory threadFactory,RejectedExecutionHandler handler) ;
corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把新到达的任务放到缓存队列当中;
maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
ArrayBlockingQueue
LinkedBlockingQueue
SynchronousQueue
PriorityBlockingQueue
threadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程做些更有意义的事情,比如设置daemon和优先级等等
handler:表示当拒绝处理任务时的策略,有以下四种取值:
1、AbortPolicy:直接抛出异常。
2、CallerRunsPolicy:只用调用者所在线程来运行任务。
3、DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
4、DiscardPolicy:不处理,丢弃掉。
5、也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。
线程池执行流程图如下:
i. 当线程数小于corePoolSize时,创建线程执行任务。
ii. 当线程数大于等于corePoolSize并且workQueue没有满时,新到达的任务放入workQueue中
iii. 线程数大于等于corePoolSize并且当workQueue满时,同时总线程数小于maximumPoolSize,则直接创建新线程执行当前任务
iv. 当线程总数等于maximumPoolSize并且workQueue已满时,执行handler的rejectedExecution。也就是拒绝策略