线程基本知识
线程的状态以及状态之间的切换
线程从创建到死亡有七个状态,分别是初始状态,准备运行,运行状态,阻塞状态,睡眠状态(超时等待状态),等待状态,死亡状态,关系如图所示:
线程的创建
实现线程的几种方式:
- 继承Thread类
- 实现Runnable接口
- 匿名内部类的方式
- 带返回值的线程
- 定时器
- 线程池实现
- Lambda表达式实现
继承Thread
// 无名
public class Demo01 extends Thread {
@Override
public void run() {
System.out.println(getName() + "执行了。。。");
}
public static void main(String[] args) {
Demo01 d1 = new Demo01();
Demo01 d2 = new Demo01();
d1.start();
d2.start();
}
}
// 有名
public class Demo01 extends Thread {
@Override
public void run() {
System.out.println(getName() + "执行了。。。");
}
public Demo01(String name) {
super(name);
}
public static void main(String[] args) {
Demo01 d1 = new Demo01("first thread");
Demo01 d2 = new Demo01("second thread");
d1.start();
d2.start();
}
}
实现Runnable接口
实现了runnable接口的类是作为一个线程任务存在的,需要将实例化后的对象传入Thread中
public class Demo02 implements Runnable {
@Override
public void run() {
System.out.println("线程启动");
}
public static void main(String[] args) {
Thread t = new Thread(new Demo02());
t.start();
}
}
匿名内部类实现
// 继承Thread类的方式
public class Demo03 {
public static void main(String[] args) {
new Thread() {
@Override
public void run() {
System.out.println("启动线程。。。");
}
}.start();
}
}
// 实现Runnable接口的方式
public class Demo03 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程启动。。。");
}
}).start();
}
}
带返回值的线程
上面创建的线程都是不带返回值以及不能抛异常的线程,接下来实现一种带返回值的线程
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Demo04 implements Callable<Integer> {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Demo04 d4 = new Demo04();
FutureTask<Integer> task = new FutureTask<>(d4);
Thread t = new Thread(task);
t.start();
System.out.println("计算结果是:" + task.get());
}
@Override
public Integer call() throws Exception {
System.out.println("正在进行紧张的计算。。。");
Thread.sleep(3000);
return 1;
}
}
定时器
定时器Timer通过schedule方法主席那个定时任务,可以指定延时多久执行,每隔多久执行,甚至是指定一个日期执行;或者是指定第一次执行的日期,然后每隔多久执行等等。
import java.util.Timer;
import java.util.TimerTask;
public class Demo05 {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
// 定时器执行的任务
System.out.println("定时器正在执行");
}
}, 0, 1000); // 延时0秒执行,每隔一秒执行一次
}
}
基于线程池实现
线程池用于降低线程创建和销毁的开销,需要线程时直接从池子里面拿,不需要时也不会销毁而是放回池子里面等待下一次使用,典型的拿空间换时间。
Executors创建
线程池最顶级的接口是Executor
,Executor里面只有一个execute方法,这个方法用于执行线程任务,但是线程池在执行完所有的任务后不会停止,需要执行shutdown()方法,但是这个方法没有在Executor中声明,而是在ExecutorService类中实现的,所以使用ExecutorService方法接收线程池对象从而执行shutdown()方法关闭线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo06 {
public static void main(String[] args) {
ExecutorService e = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i ++) {
e.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
e.shutdown();
}
}
上面是通过Executors静态工厂创建的线程池,Executors一共提供了五个核心方法,分别是:
- Executors.newWorkStealingPool():这个方法在JDK1.8中引入,创建持有足够线程的线程池支持给定的并行度,并通过使用多个队列减少竞争
- Executors.newCachedThreadPool():maximumPoolSize最大可以到Integer.MAX_VLUE,线程数不够会自动增加,比较弹性。存在OOM异常,能够回收不工作的线程,keepAliveTime默认60,也就是60秒回收一个空闲线程
- Executors.newScheduledThreadPool():线程数最大至Integer.MAX_VLUE,同样存在OOM风险,支持定时及周期性执行任务,优于Timer,不会回收不工作的线程
- Executors.newSingleThreadExecutor():创建一个单线程的线程池,相当于单线程串行执行所有任务,保证按任务的提交顺序依次执行
- Executors.newFixedThreadPool():输入的参数就是固定的线程数,既是核心线程数也是最大线程数,不存在空闲线程,所以keepAliveTime为0
使用ThreadPoolExecutor创建
一般不推荐使用Executors创建二十建议使用ThreadPollExecutor,因为Executors中的方法允许的请求队列长度是Integer.MAX_VALUE,可能会导致堆积大量的请求,从而导致OOM
ThreadPoolExecutor的构造方法由7个参数:
- corePoolSize:表示核心线程数,核心线程在任务执行完毕后不会销毁
- maximumPoolSize:线程池能够容纳的最大线程数,必须大于1,如果执行的任务数超过了该值,多出的任务数会被放入队列中等待执行
- keepAliveTime:表示线程池中允许线程空闲的时间,当空闲时间达到keepAliveTime时该线程就会被销毁,直到池中只有corePoolSize数量的线程为止。如果想让核心线程超时后也被回收,只需要将ThreadPoolExecutor的allowCoreThreadTimeOut变量设置为true
- TimeUnit:表示keepAliveTime的时间单位,通常时TimeUnit.SECONDS
- workQueue:缓存队列,当请求的任务数大于maxiumPoolSize后,超过的任务会进入BlockingQueue阻塞队列中
- threadFactory:用于生产一组相同任务的线程,线程的命名就是通过给这个factory增加组名前缀来实现的
- handler:执行拒绝策略的对象,当执行的任务数超过第五个参数workQueue的缓存限制时,及就可以使用该策略处理请求,比方说将请求转向某个提示页面表示当前执行任务数过多
上面的队列、线程工厂、拒绝处理服务都必须有实例对象,但是再实际编程中都是通过Executors这个线程池静态工厂提供默认的实现,Executors用法上面已经说明,那么现在使用ThreadPoolExecutor来创建一个线程池
第一步:实现队列,线程工厂,拒绝服务
// 线程工厂
class UserThreadFactory implements ThreadFactory {
private final String namePrefix;
private final AtomicInteger nextId = new AtomicInteger(1);
UserThreadFactory(String name) {
namePrefix = "该线程来自:" + name + "----这是该线程池中的第";
}
/**
* 该方法用于创建线程池中的线程
* @param r
* @return
*/
@Override
public Thread newThread(Runnable r) {
String name = namePrefix + nextId.getAndIncrement() + "个线程";
Thread thread = new Thread(null, r, name, 0);
System.out.println(thread.getName()); // 打印线程名称
return thread;
}
}
// 拒绝服务实现
class UserRejectHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("任务太多啦,被拒绝了" + executor.toString());
}
}
第二步:实现线程任务
class Task implements Runnable {
@Override
public void run() {
System.out.println("执行中。。。");
}
}
创建线程池
public class Demo07 {
public static void main(String[] args) {
// 设置了缓存队列的大小为2
ThreadPoolExecutor tpe = new ThreadPoolExecutor(1, 2, 60, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(2), new UserThreadFactory("线程池工厂一"), new UserRejectHandler());
for (int i = 0; i < 200; i++) {
tpe.execute(new Task());
}
}
}
Lambda表达式实现
使用parallelStream()实现