什么是线程?
线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
线程的状态:
- 新建(NEW):新创建了一个线程对象。
- 可运行(RUNNABLE):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
- 运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
- 阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:
- 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
- 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
- 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
- 死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
实现线程的方式
1.继承Thread类
package com.sk.Multithreading;
public class ThreadDemo {
public static void main(String[] args) {
MyThread thread1 = new MyThread("玉无双");
MyThread thread2 = new MyThread("花辞树");
MyThread thread3 = new MyThread("醉星河");
thread1.start();//启动线程,只能调用一次start()方法,否则会抛出java.lang.IllegalThreadStateException
thread2.start();
thread3.start();
}
}
class MyThread extends Thread {
String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name+Thread.currentThread().getName());
}
}
start方法的作用就是将线程由NEW状态,变为RUNABLE状态。start()方法内部调用的是start0()native方法启动。当线程创建成功时,线程处于NEW(新建)状态,如果你不调用start( )方法,那么线程永远处于NEW状态。(每个线程类对象只能调用一次)调用start( )后,才会变为RUNABLE状态,线程才可以运行。(不一定就会立即执行,线程要等待CPU调度)。当线程被cpu调度,就会让线程去执行run方法。
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
2.实现Runnable接口
Java里的继承都是单一的有局限性,所以我们可以通过实现Runnable 接口来创建线程(Thread类实现了Runnable 接口)。
package com.sk.Multithreading;
public class ThreadDemo {
public static void main(String[] args) {
Thread thread1 = new Thread(new MyThread("玉无双"));//传入一个实现Runnable 接口的对象
Thread thread2 = new Thread(new MyThread("花辞树"));
Thread thread3 = new Thread(new MyThread("醉星河"));
thread1.start();
thread2.start();
thread3.start();
}
}
class MyThread implements Runnable {
String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name+Thread.currentThread().getName());
}
}
3.实现Callable接口
Runnable接口有一个缺点:当线程执行完毕后,我们无法获取一个返回值。并且Runnable接口不会向上抛出异常,只能在run()方法里进行try catch,而Callbale可以向上抛异常,并且定义的时候可以设置一个泛型,此泛型的类型就是返回数据的类型。
package com.sk.Multithreading;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> task1 = new FutureTask(new MyThread("玉无双"));
FutureTask<String> task2 = new FutureTask(new MyThread("花辞树"));
FutureTask<String> task3 = new FutureTask(new MyThread("醉星河"));
new Thread(task1).start();
String s1 = task1.get();
System.out.println(s1);
new Thread(task2).start();
String s2 = task2.get();
System.out.println(s2);
new Thread(task3).start();
String s3 = task3.get();
System.out.println(s3);
}
}
class MyThread implements Callable<String> {
String name;
public MyThread(String name) {
this.name = name;
}
@Override
public String call() throws Exception {
System.out.println(name+Thread.currentThread().getName());
return name;
}
}
4.线程池
4.1实现方式
1、newCachedThreadPool:用来创建一个可以无限扩大的线程池,适用于负载较轻的场景,执行短期异步任务。(可以使得任务快速得到执行,因为任务时间执行短,可以很快结束,也不会造成cpu过度切换)
package com.sk.Multithreading;
import java.util.concurrent.*;
public class ThreadDemo {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newCachedThreadPool();
try {
for (int i = 1; i <= 10; i++) {//模拟10个任务
threadPool.execute(() -> System.out.println(Thread.currentThread().getName() + "处理任务"));
//TimeUnit.MILLISECONDS.sleep(100);//休息100ms
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
效果
休息100ms,看线程是否可以复用。
2、newFixedThreadPool:创建一个固定大小的线程池,因为采用无界的阻塞队列,所以实际线程数量永远不会变化,适用于负载较重的场景,对当前线程数量进行限制。(保证线程数可控,不会造成线程过多,导致系统负载更为严重)
package com.sk.Multithreading;
import java.util.concurrent.*;
public class ThreadDemo {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(5);
try {
for (int i = 1; i <= 10; i++) {//模拟10个任务
threadPool.execute(() -> System.out.println(Thread.currentThread().getName() + "处理任务"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
效果
3、newSingleThreadExecutor:创建一个单线程的线程池,适用于需要保证顺序执行各个任务。
package com.sk.Multithreading;
import java.util.concurrent.*;
public class ThreadDemo {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor();
try {
for (int i = 1; i <= 10; i++) {//模拟10个任务
threadPool.execute(() -> System.out.println(Thread.currentThread().getName() + "处理任务"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
效果
4、newScheduledThreadPool:适用于执行延时或者周期性任务。
package com.sk.Multithreading;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.*;
public class ThreadDemo {
public static void main(String[] args) {
ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(3);
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
System.out.println("时间:"+sdf.format(new Date()));
try {
for (int i = 1; i <= 10; i++) {//模拟10个任务
threadPool.schedule(new Runnable(){
@Override
public void run() {
System.out.println("时间:" + sdf.format(new Date())+Thread.currentThread().getName()+"处理任务");
}
},3,TimeUnit.SECONDS);//延时3秒后执行
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
效果
5、newWorkStealingPool:可以传入线程的数量,不传入,则默认使用当前计算机中可用的cpu数量,适用于耗时严重的任务。(Java8新增的)
package com.sk.Multithreading;
import java.util.concurrent.*;
public class ThreadDemo {
public static void main(String[] args) {
ExecutorService stealingPool = Executors.newWorkStealingPool();
try {
for (int i = 1; i <= 10; i++) {//模拟10个任务
int finalI = i;
stealingPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + finalI + "处理任务");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
stealingPool.shutdown();
}
while (Thread.activeCount() > 2) {
}
}
}
效果
4.2参数详解
1、corePoolSize(核心线程池基本大小):当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,(除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。)
2、maximumPoolSize(线程池最大大小):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。
配置线程池最大线程数
- CPU密集型(任务需要大量运算,没有阻塞,CPU一直全速运行)最大线程数=CPU核数+1
- IO密集型(任务线程并不是一直在执行任务):最大线程数 = CPU核数*2 或 最大线程数 = CPU核数/(1-阻塞系数)(阻塞系数的值一般在0.8~0.9)
3、keepAliveTime(线程存活保持时间)当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。
4、unit(单位)keepAliveTime参数的时间单位。
5、workQueue(任务队列):用于传输和保存等待执行任务的阻塞队列。
6、threadFactory(线程工厂):用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。
7、handler(拒绝策略4种):当线程池和队列都满了,再加入线程会执行此策略。
- ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。(默认)
- ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
- ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
- ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
注意:线程池Executors创建线程底层其实调用的是ThreadPoolExecutor 类去创建。阿里开发手册强制:线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
以下是阿里手册规范
线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这
样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2) CachedThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters and default thread factory and rejected execution handler.
* It may be more convenient to use one of the {@link Executors} factory
* methods instead of this general purpose constructor.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
4.3线程池运行流程
1、判断核心线程池是否已满,没满则创建一个新的工作线程来执行任务。满了则加入任务队列中。
2、判断任务队列是否已满,没满则将新提交的任务添加在工作队列,满了则启动最大线程数的线程来执行任务。
3、判断整个线程池是否已满,没满则创建一个新的工作线程来执行任务,已满则执行拒绝策略。