线程基本介绍
什么是线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中, 是进程中的实际运作单位
为什么会有线程
在多核CPU中,利用多线程可以实现真正意义上的并行执行
在一个应用进程中,会存在多个同时执行的任务,如果其中一个任务被阻塞,将会引起不依赖该任务的任务也被阻塞。通过对不同任务创 建不同的线程去处理,可以提升程序处理的实时性
线程可以认为是轻量级的进程,所以线程的创建、销毁比进程更快
线程的应用场景
使用多线程实现文件下载
后台任务:如定时向大量(100W以上)的用户发送邮件
异步处理:记录日志
多步骤的任务处理,可根据步骤特征选用不同个数和特征的线程来协作处理,多任务的分割,由一个主线程分割给多个线程完成
总结
多线程的本质是:合理的利用多核心CPU资源来实现线程的并行处理,来实现同一个进程内的多个任务的并行执行,同时基于线程本身的异步执行特性,提升任务处理的效率
Java中三种创建方式
继承Thread类
public class ThreadDemo extends Thread{
@Override
public void run() {
System.out.println("当前线程:"+ Thread.currentThread().getName());
}
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
threadDemo.start(); //启动一个线程
}
}
实现Runnable接口
public class RunnableDemo implements Runnable {
@Override
public void run() {
System.out.println("当前线程:"+ Thread.currentThread().getName());
}
public static void main(String[] args) {
RunnableDemo runnableDemo = new RunnableDemo();
Thread thread = new Thread(runnableDemo);
thread.start(); //启动一个线程
}
}
实现Callable接口
public class CallableDemo implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("当前线程:"+ Thread.currentThread().getName());
return "无聊人";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<String> future = executorService.submit(new CallableDemo());
//future.get() 是一个阻塞方法
System.out.println(Thread.currentThread().getName() + "-----" + future.get());
}
}
线程的生命周期
Java线程从创建到销毁,一共经历6个状态
NEW:初始状态,线程被构建,但是还没有调用start方法
RUNNABLED:运行状态,JAVA线程把操作系统中的就绪和运行两种状态统一称为“运行中”
BLOCKED:阻塞状态,表示线程进入等待状态,也就是线程因为某种原因放弃了CPU使用权,阻塞也分为几种情况
WAITING: 等待状态
TIME_WAITING:超时等待状态,超时以后自动返回
TERMINATED:终止状态,表示当前线程执行完毕
public class ThreadStatusDemo {
public static void main(String[] args) {
//TIME_WAITING
new Thread(() -> {
while (true) {
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "WLR_TIME_WAITING").start();
//WAITING
new Thread(() -> {
while (true) {
synchronized (ThreadStatusDemo.class) {
try {
ThreadStatusDemo.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "WLR_WAITING").start();
//BLOCKED
new Thread(new BlockedDemo(), "Blocked01").start();
new Thread(new BlockedDemo(), "Blocked02").start();
//在target中找到对应class文件 cmd中 jps找到运行进程id 通过 jstack id 找到运行日志
}
static class BlockedDemo extends Thread {
@Override
public void run() {
synchronized (BlockedDemo.class) {
while (true) {
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
线程基本操作及原理
Thread.join使用及原理
Thread.join的作用是保证线程执行结果的可见性
public class ThreadJoinDemo {
private static int x = 0;
private static int i = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
i = 1;
x = 2;
});
Thread t2 = new Thread(() -> {
i = i + x;
});
t1.start();
t1.join(); //t1线程的执行结果对t2可见(t1线程一定要比t2线程优先执行) --阻塞
t2.start();
Thread.sleep(1000);
System.out.println("----" + i);
}
}
Thread.join的本质其实是wait/notifyall
让主线程进入阻塞状态,join底层就是使用了wait进行了等待,有wait就必然会有 notify/notify_all,notify并不是在java代码层面中实现的,他是在jvm实现的,线程的销毁都是在jvm中实现的,join调用完在销毁时jvm中有一个ensure_join(this); 方法会去实现lock.notify_all(thread); 进行唤醒阻塞下的主线程
thread.sleep
使线程暂停执行一段时间,直到等待的时间结束才恢复执行或在这段时间内被中断
工作流程:
挂起线程并修改其运行状态
用sleep()提供的参数来设置一个定时器。
当时间结束,定时器会触发,内核收到中断后修改线程的运行状态。
例如线程会被标志为就绪而进入就绪队列等待调度
问题思考:
- 假设现在是 2019-11-18 12:00:00.000,如果我调用一下
Thread.Sleep(1000) ,在 2019-11-18 12:00:01.000 的时候,这个线程会
不会被唤醒?
该线程在未来1秒来退出cpu,不参与cpu竞争,但是在一秒过后这个时候也许另外一个线程正在使用cpu,那么这个时候操作系统是不会重新分配cpu的,直到那个线程挂起或者结束,就算正好轮到操作系统分配cpu,当前线程也不一定是那个优先级最高的那个,cpu可能还是会被其他的线程抢占过去
-
Thread.Sleep(0) 的意义
类似于Thread.yield() 出让cpu 触发操作系统,重新进行cpu竞争
线程的调度算法
操作系统中,CPU竞争有很多种策略。Unix系统使用的是时间片算法,而Windows则属于抢占式的。
wait和notify
wait:作用是挂起当前线程,释放获取到的锁,直到别的线程调用了这个对象的notify或notifyAll方法。
notify:作用是唤醒因调用wait挂起的线程,如果有过个线程,随机唤醒一个。
notifyAll:作用是唤醒全部因调用wait挂起的线程。
通俗一点就是wait进行阻塞线程,notify进行唤醒线程
例如当在处理生产者和消费者的时候
一个线程修改了一个对象的值,而另个线程感知到了变化,然后进行响应的操作
//生产者
public class Producer implements Runnable {
private Queue<String> bags;
private int size;
public Producer(Queue<String> bags, int size) {
this.bags = bags;
this.size = size;
}
@Override
public void run() {
int i = 0;
while (true) {
i++;
synchronized (bags) {
while (bags.size() == size) {
System.out.println("bags已经满了");
//bags达到生产最大值 进行阻塞
try {
bags.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("生产者-生产:bag" + i);
bags.add("bag" + i); //生产
// 唤醒处于阻塞状态下的消费者
bags.notifyAll();
}
}
}
}
//消费者
public class Consumer implements Runnable {
private Queue<String> bags;
private int size;
public Consumer(Queue<String> bags, int size) {
this.bags = bags;
this.size = size;
}
@Override
public void run() {
while (true) {
synchronized (bags) {
while (bags.isEmpty()) {
System.out.println("bags为空");
try {
bags.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String bag = bags.remove();
System.out.println("消费者消费:" + bag);
// 唤醒处于阻塞状态下的生产者
bags.notifyAll();
}
}
}
}
//测试
public class WaitNotifyDemo {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
int size = 10;
Producer producer = new Producer(queue, size);//生产者
Consumer consumer = new Consumer(queue, size);//消费者
Thread t1 = new Thread(producer);
Thread t2 = new Thread(consumer);
t1.start();
t2.start();
}
}
为什么wait/notify需要加synchronized
1. 其实wait/notify本质上其实是一种条件的竞争,至少来说,wait和notify方法一定是互斥存在的,既然要实现互斥,那么synchronized就是一个很好的解决方法
2. wait和notify是用于实现多个线程之间的通信,而通信必然会存在一个通信的载体,比如我们小时候玩的游戏,用两个纸杯,中间用一根线连接,然后可以实现 比较远距离的对话。而这根线就是载体,那么在这里也是一样,wait/notify是基 于synchronized来实现通信的。也就是两者必须要在同一个频道也就是同一个锁的 范围内
Thread.interrupted Thread.interrupt
interrupt
如何正确终止一个线程
很多人会想到Thread.stop,不过现在被弃用了
stop底层用的是用的是native方法,相当于在操作系统层面上,jvm层面上直接去把这个线程给掐断,类似Linux中去结束一个进程,kill -9 这种方法是有损害的对 同时也会导致结果的不准确性
存在场景(while循环,线程处于阻塞状态下 中断才有意义)
interrupt方法(中断比较友好) interrupt(Java中看不到该源码存在于jvm中)
public class InterruptDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {//判断中断标识 默认是false
System.out.println("无聊人");
}
});
thread.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();//中断(友好)把中断标识由false->true
}
}
当其他线程通过调用当前线程的interrupt方法,表示向当前线程打个招呼,告诉他可以中断线程的执行了,至于什么时候中断,取决于当前线程自己
interrupted
Thread.interrupted()对设置中断标识的线程复位,并且返回当前的中断状态
public class InterruptedDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (true) {
//当为true 标识被中断过
if (Thread.currentThread().isInterrupted()) {
System.out.println("中断" + Thread.currentThread().isInterrupted());
Thread.interrupted();
System.out.println("复位" + Thread.currentThread().isInterrupted());
}
}
});
thread.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();//中断
}
}
线程的安全性分析
什么叫线程安全
当多个线程访问某个对象时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或者协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的
原子性 可见性 有序性
并发编程问题的源头-原子性 可见性 有序性
-
原子性
即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
-
可见性
是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
-
有序性
即程序执行的顺序按照代码的先后顺序执行
在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性
Java内存模型
JMM
Java内存模型是一种抽象结构,它提供了合理的禁用缓存以及禁止重排序
的方法来解决可见性、有序性问题
可见性、有序性问题 解决方案
synchronized、Volatile、final关键字
Happens-Before原则
同步关键字synchronized
作用
可以解决可见性、原子性、有序性问题
范围
- 对于普通同步方法,锁是当前实例对象。
//javap -v xxx.class
//对象锁(同一个对象有用) 同一个对象调用才会实现互斥
public synchronized void demo(){
}
- 对于静态同步方法,锁是当前类的Class对象。
//类级别锁 xxx.class 不同对象也会发生互斥 互斥必须有同一个资源进行争夺
public synchronized static void demo1(){
}
-
对于同步方法块,锁是Synchonized括号里配置的对象。
//范围可控 synchronized(this){//对象锁 } /*************/ synchronized(xxx.class){//类级别锁 }
java6之前都叫重量级锁
优化
自适应自旋锁
引入偏向锁、轻量级锁
锁消除、锁粗化
Volatile
作用
volatile可以用来解决可见性和有序性问题
解决可见性
Lock指令的作用
硬件层面提供的
将当前处理器缓存行的数据写回到系统内存。 这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。(就是禁用缓存)
什么情况下用到volatile
当存在多个线程对同一个共享变量进行修改的时候,
需要增加volatile,保证数据修改的实时可见
解决有序性
CPU层面的内存屏障
Store Barrier:强制所有在store屏障指令之前的store指令,都在
该store屏障指令执行之前被执行,并把store缓冲区的数据都刷到CPU缓存
Load Barrier:强制所有在load屏障指令之后的load指令,都在该load屏障
指令执行之后被执行,并且一直等到load缓冲区被该CPU读完才能执行之后的
load指令
Full Barrier:复合了load和storee屏障的功能
什么是内存屏障
总结
本质上来说:volatile实际上是通过内存屏障来防止指令重排序以及
禁止cpu高速缓存来解决可见性问题。
而#Lock指令,它本意上是禁止高速缓存解决可见性问题,但实际上在
这里,它表示的是一种内存屏障的功能。也就是说针对当前的硬件环境,
JMM层面采用Lock指令作为内存屏障来解决可见性问题
final
final在Java中是一个保留的关键字,可以声明成员变量、方法、类以
及本地变量。一旦你将引用声明作final,你将不能改变这个引用了
final域和线程安全
首先要防止溢出带来的重排序问题,防止"逃逸"
对于final域,编译器和处理器要遵守两个重排序规则。
在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一 个引用变量,这两个操作之间不能重排序。
初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操
作之间不能重排序。
写final域重排序规则
JMM禁止编译器把final域的写重排序到构造函数之外。
编译器会在final域的写之后,构造函数return之前,插入一个StoreStore屏障。这个屏障禁止处理器把final域的写重排序到构造 函数之外
读域的重排序规则
在一个线程中,初次读对象引用与初次读该对象包含的final域,JMM禁止处理器重排序这两个操作,编译器会在读final域操作的前面插入一个LoadLoad屏障。
Happens-Before
Happens-Before是一种可见性规则,它表达的含义是前面一个操作的结果对后续操作是可见的
6种Happens-Before规则
程序顺序规则
监视器锁规则
Volatile变量规则
传递性
start()规则
Join()规则
Atomic
原子类Atomic-无锁工具的典范
public class AtomicDemo {
private static AtomicInteger atomicInteger = new AtomicInteger();
public static void incr() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicInteger.incrementAndGet();
}
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
new Thread(AtomicDemo::incr).start();
}
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("----" + atomicInteger.get());
}
}
ThreadLocal
public class ThreadLocalDemo {
public static final ThreadLocal<Integer> LOCAL = new ThreadLocal<Integer>() {
protected Integer initialValue() {
return 0; //初始值
}
};
public static void main(String[] args) {
Thread[] threads = new Thread[5];
//希望每个线程拿到的都是0
for (int i = 0; i < 5; i++) {
threads[i] = new Thread(() -> {
int num = LOCAL.get();//拿到初始值
num += 5;
LOCAL.set(num);
System.out.println(Thread.currentThread().getName() + "->" + num);
}, "Thread-" + i);
}
for (Thread t : threads) {
t.start();
}
}
}
发布与逃逸
发布对象
发布的意思是使一个对象能够被当前范围之外的代码所使用
不安全发布
对象溢出
一种错误的发布,当一个对象还没有构造完成时,就使它被其他线程所见
安全发布对象的四种方法
在静态初始化函数中初始化一个对象引用
将对象的引用保存到volatile类型的域或者AtomicReference对象中(利用volatile happen-before规则)
将对象的引用保存到某个正确构造对象的final类型域中(初始化安全性)
将对象的引用保存到一个由锁保护的域中(读写都上锁)
JUC 之 AQS
重入锁ReentrantLock
一个持有锁的线程,在释放锁之前,如果再次访问加了该同步锁的其他方法, 这个线程不需要再次争抢锁,只需要记录重入次数
什么是锁
锁是用来解决多线程并发访问共享资源所带来的数据安全性问题的手段。对一个共享资源加锁后,如果有一个线程获得了锁,那么其他线程无法访问这个共享资源
public class LockDemo {
static Lock lock = new ReentrantLock();//重入锁 解决死锁问题
public static int count = 0;
public static void incr() {//递增
try {
lock.lock();//获得锁
Thread.sleep(1);
decr();
count++;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();//释放锁
}
}
public static void decr() {//递减
lock.lock();//有需要争抢锁 (不需要争抢锁,记录重入次数)
count--;
lock.unlock();
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
new Thread(LockDemo::incr).start();
}
Thread.sleep(4000);
System.out.println("-------" + count);
}
}
AQS
CountDownLatch
countdownlatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完毕再执行。从命名可以解读到countdown是倒数的意思,类似于我们倒计时的概念。
public class CountDownLatchDemo {
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(3);
new Thread(()->{
countDownLatch.countDown(); //倒计时 3 - 1 = 2
}).start();
new Thread(()->{
countDownLatch.countDown(); //倒计时 2 - 1 = 1
}).start();
new Thread(()->{
countDownLatch.countDown(); //倒计时 1 - 1 = 0
}).start();
try {
countDownLatch.await();
System.out.println("线程执行结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class CountDownLatchDemo01 implements Runnable{
static CountDownLatch countDownLatch = new CountDownLatch(1);
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
new Thread(new CountDownLatchDemo01()).start();
}
countDownLatch.countDown();
}
@Override
public void run() {
try {
countDownLatch.await();//阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Semaphore
semaphore也就是我们常说的信号灯 semaphore可以控制同时访问的线程个数 通过acquire获取一个许可 如果没有就等待 通过release释放一个许可 有点类似限流的作用 叫信号的的原因也和他的用处有关 比如某商场就5个停车位 每个停车位只能停一辆车如果这个时候来了10辆车 必须要等前面有空的车位才能进入
public class SemaphoreDemo {
public static void main(String[] args) {
//当前可以获得最大许可数量是5个
Semaphore semaphore = new Semaphore(5);
for (int i = 0; i < 10; i++) {
new car(i, semaphore).start();
}
}
static class car extends Thread {
private int num;
private Semaphore semaphore;
public car(int num, Semaphore semaphore) {
this.num = num;
this.semaphore = semaphore;
}
@Override
public void run() {
try {
semaphore.acquire();//获得一个许可
System.out.println("第" + num + "占用一个车位");
TimeUnit.SECONDS.sleep(2);
System.out.println("第" + num + "辆车走了");
semaphore.release();//释放许可
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
CyclicBarrier
CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开启,所有被屏障拦截的线程才会继续工作。
当存在需要所有的子任务都完成时,才执行主任务,这个时候就可以选择使用CyclicBarrier
public class DataImportThread extends Thread {
private String path;
private CyclicBarrier cyclicBarrier;
public DataImportThread(String path, CyclicBarrier cyclicBarrier) {
this.path = path;
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
System.out.println("导入:" + path + "位置的数据");
try {
cyclicBarrier.await();//阻塞
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
public class CycliBarrierDemo extends Thread {
@Override
public void run() {
System.out.println("开始汇总");
}
/**
* parties 如果因为某种原因导致没有足够多的线程调用await 这个时候会导致所有线程都会被阻塞
* await(timeout, unit) 设置一个超时等待时间
* reset重置计数 会抛出一个BrokenBarrierException异常
*
* @param args
*/
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3, new CycliBarrierDemo());
new DataImportThread("path1", cyclicBarrier).start();
new DataImportThread("path2", cyclicBarrier).start();
new DataImportThread("path3", cyclicBarrier).start();
//结束后做一个汇总
}
}
Condition
Condition是一个多线程协调通信的工具类,可以让某些线程一起等待某个条件(condition),只有满足条件时,线程才会被唤醒
public class ConditionWaitDemo extends Thread {
private Lock lock;
private Condition condition;
public ConditionWaitDemo(Lock lock, Condition condition) {
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
System.out.println("开始无聊人wait");
lock.lock();
try {
condition.await();
System.out.println("结束无聊人wait");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ConditionNotifyDemo extends Thread {
private Lock lock;
private Condition condition;
public ConditionNotifyDemo(Lock lock, Condition condition) {
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
System.out.println("开始无聊人Notify");
lock.lock();
condition.signal();
System.out.println("结束无聊人Notify");
lock.unlock();
}
}
public class ConditionDemo {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
ConditionWaitDemo conditionWaitDemo = new ConditionWaitDemo(lock, condition);
ConditionNotifyDemo conditionNotifyDemo = new ConditionNotifyDemo(lock, condition);
conditionWaitDemo.start();
conditionNotifyDemo.start();
}
}
线程池
线程池基本认识
什么是线程池
提前创建好若干个线程放在一个容器中。如果有任务需要处理,则将任务直接分配给线程池中的线程来执行,任务处理完以后这个线程不会被销毁,而是等待后续分配任务
线程池的好处
降低创建线程和销毁线程的性能开销
提高响应速度,当有新任务需要执行是不需要等待线程创建就可以立马执行
合理的设置线程池大小可以避免因为线程数超过硬件资源瓶颈带来的问题
java中常见的线程池
newFixedThreadPool 创建一个固定的线程池
newSingleThreadExecutor 创建只有一个线程的线程池
newCachedThreadPool 根据实例去调整的线程池 动态调整 可伸缩
newScheduledThreadPool 带定时任务的线程池 延迟性 周期性
public class ThreadPoolDemo implements Runnable {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 100; i++) {
executorService.execute(new ThreadPoolDemo());
}
executorService.shutdown();
}
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
线程池的构造方法
public ThreadPoolExecutor( int corePoolSize, //核心线程数量
int maximumPoolSize,//最大线程数
long keepAliveTime, //超时时间,超出核心线程数量以外的线程空余存活时间
TimeUnit unit, //存活时间单位
BlockingQueue<Runnable> workQueue, //保存执行任务的队列
ThreadFactory threadFactory, //创建新线程使用的工厂
RejectedExecutionHandler handler //当任务无法执行的时候的处理方式
)
线程池原理
对线程池监控
可以自定义线程池 继承ThreadPoolExecutor类
public class ThreadPoolSelf extends ThreadPoolExecutor {
public ThreadPoolSelf(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
public void shutdown() {
super.shutdown();
}
//启动前
@Override
protected void beforeExecute(Thread t, Runnable r) {
System.out.println(System.currentTimeMillis());
}
//启动后
@Override
protected void afterExecute(Runnable r, Throwable t) {
System.out.println(System.currentTimeMillis());
System.out.println("初始线程数" + this.getPoolSize());
System.out.println("核心线程数" + this.getCorePoolSize());
System.out.println("正在执行的任务数量" + this.getActiveCount());
System.out.println("已经执行的任务数" + getCompletedTaskCount());
System.out.println("任务总数" + this.getTaskCount());
}
}
public class ExecutorSelf {
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolSelf(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
}
public class ThreadPoolDemo implements Runnable {
public static void main(String[] args) {
ThreadPoolExecutor executorService = (ThreadPoolExecutor) ExecutorSelf.newFixedThreadPool(3);
executorService.prestartAllCoreThreads(); //可以提前预热所有核心线程
for (int i = 0; i < 100; i++) {
executorService.execute(new ThreadPoolDemo());
}
executorService.shutdown();
}
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
带返回值的线程
public class CallableFutureDemo implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("无聊人");
return "wlr";
}
public static void main(String[] args) {
CallableFutureDemo callableFutureDemo = new CallableFutureDemo();
FutureTask futureTask = new FutureTask(callableFutureDemo);
new Thread(futureTask).start();
try {
//get方法是属于阻塞方法
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
死锁
死锁发生的条件
互斥,共享资源 X 和 Y 只能被一个线程占用;
占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X;
不可抢占,其他线程不能强行抢占线程 T1 占有的资源;
循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待。
如何解决死锁问题
一旦发生死锁,一般没什么好的方法来解决,只能通过重启应用。所以如果要解决死锁问题,最好的方式就是提前规避