目录
线程池(创建线程池的两种方式)
线程概念
进程:
1. 是指内存中运行的应用程序
2. 每个进程都有一个独立的内存空间
3. 一个应用程序可以同时运行多个进程
线程:
1. 是指进程中的一个子程序
2. 进程中的一条执行路径/执行单元
3. 线程也有独立的内存空间
并行: 同一时刻, 多个程序一起执行
并发: 同一时刻,多个程序交替执行
创建线程
1. 如何创建线程对象
public class ThreadDemo extends Thread{
//1.要让自定义线程和thread有联系
//2.重写线程中的run方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.getName()+"跑了:"+i+"米.");
}
}
public ThreadDemo(String name) {
super(name);
}
public ThreadDemo() {
}
}
class Test {
public static void main(String[] args) {
//3.创建自定义线程对象
ThreadDemo threadDemo = new ThreadDemo();
//4.调用线程的start方法, 启动线程
threadDemo.start();
}
}
2. 使用Runnable创建线程任务, 解耦合
public class RunnableDemo implements Runnable{
// 1.实现Runnable接口
// 2.实现run方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
// Thread.currentThread().setName("李四");
// 使用Thread类中的静态获取方法,调用的线程名
System.out.println(Thread.currentThread().getName()+"执行了"+i+"次循环.");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Test {
public static void main(String[] args) {
//Thread的静态方法.currentTread获取当前线程对象 , 从而获取线程名/ 设置线程名
// 3.创建Runnable线程任务
// 4.创建Thread线程, 将线程任务放进去
RunnableDemo runnableDemo = new RunnableDemo();
Thread thread = new Thread(runnableDemo);
thread.start();
//也可以通过线程的构造参数 , 传入线程名
Thread thread1 = new Thread(runnableDemo,"张三");
thread1.start();
}
}
3.使用Callable接口, 执行带有返回值的线程任务
public class CallableTask implements Callable<Integer> {
// 1.创建Callable实现类, 实现Callable接口, 明确泛型
// 2.实现call方法
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum+=i;
}
return sum;
}
}
class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 3.创建Callable实现类对象
CallableTask callableTask = new CallableTask();
// 4.封装一个FutureTask 装载带有返回值的Callable对象
// 因为thread的start方法没有返回值, call是有返回值的, 因此需要futureTask进行包装
FutureTask<Integer> futureTask = new FutureTask<>(callableTask);
// 5.创建thread线程对象, 装载futureTask对象
Thread thread = new Thread(futureTask);
// 6.启动线程
thread.start();
// 7.使用futureTask的get方法, 获取到返回值
Integer integer = futureTask.get();
System.out.println(integer);
}
}
常用方法
设置 / 获取线程对象的名字
Thread.currentThread().setName("李四");
Thread.currentThread().getName();
Tread线程对象的sleep(): 让当前执行代码的线程休眠指定毫秒数
存在编译器异常需要try..catch处理
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
为什么Thread和Runnable中不能抛出异常, 而main方法却可以?
实现类实现接口/ 子类继承父类:
方法重写里面子类不能抛出比父类更大的异常.如果父类的方法里面没抛. 子类重写不能抛.
父类方法声明的时候抛出了异常. 子类重写可以抛也可以不抛.
内存结构
MyThread myThread = new MyThread();
myTread.start();
创建一个线程对象, 调用start(), 通知Java虚拟机为当前线程开辟一个线程栈用于存储当前线程的数据 , 栈内存相当于主线程的线程栈
共享资源
使用Thread类重写run方法, 是不能完成同一个线程任务的两个线程共享资源的效果的
使用Runnable接口, 共享资源:
1. 将共享资源定义成(静态)成员变量
2. 初始化成员变量, 或者创建包含该成员变量的构造方法
解决线程不安全问题(加锁)
多线程在访问共享资源的过程中会出现线程不安全的问题:
线程不安全问题出现的前提: 1. 多线程 2.有共享资源 3.对共享资源修改
加锁的三种方式: 同步代码块/ 同步方法 / lock锁.
同步代码块: 锁对象只能是多线程公用的对象, 一人一个锁达不到加锁的效果
同步方法: 将加锁的部分抽取出来成一个锁方法
lock锁: 将锁封装成对象, 通过手动调用加锁和解锁的方法控制锁的范围
加锁过程中的异常
在同步代码块和同步方法中遇到运行时异常, 会终止当前线程,释放锁和cpu执行权, 其他线程仍能继续执行
在使用lock锁对象时遇到运行时异常, 是没有机会执行到解锁方法的, 没有释放锁和cpu执行权, 其他线程也就不能继续执行, 因此程序停止...
针对这种情况, 我们需要无论如何也要让解锁的方法执行.try...catch...finally 代码块就符合这种要求,finally也经常应用在一些释放资源的场景中
public class TryCatchFinallyDemo1 implements Runnable{
private int ticket = 100;
private static final Lock LOCK =new ReentrantLock();
@Override
public void run() {
while (true){
try {
//一直卖票 , 窗口不关
LOCK.lock();
if (ticket>0){
//当票卖完, 则不卖了
try {
//模拟卖票等待时间
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (Thread.currentThread().getName().equals("窗口B")&& ticket==50){
throw new RuntimeException("窗口B 正在卖第50张票!");
}
//当使用lock锁遇到异常时, 来不及运行释放锁的代码
//这个时候可以使用try catch finally 将无论如何也要执行的释放锁代码 放在finally中
System.out.println(Thread.currentThread().getName()+":当前再买第"+ticket+"张票.");
ticket--;
}
} finally {
// 将无论如何也要执行的释放锁代码 放在finally中
LOCK.unlock();
}
}
}
}
finally和return的应用
public class Test {
public static void main(String[] args) {
int result = finallyReturn();
System.out.println(result);
}
public static int finallyReturn(){
try {
return 100;
}finally {
return 200;
}
}
}
这段代码执行的时候是会先返回100到缓存区, 最后执行finally返回200,将之前的返回值冲掉
死锁
概念:
存在两把或以上的锁, 多个线程持有对方线程需要的锁, 又在等对方释放锁
发现死锁:
使用java工具 jps查看线程栈编号, 再通过jstack 栈编号 查看对应线程状态,以及锁情况
处理:
1.加锁: 在最外层加一把锁, 谁拿到谁先执行(性能低)
2.减锁: 存在无意间多加锁的情况
3.加锁的顺序: 统一加锁的顺序, 避免卡死
线程的六种状态
- New: 创建新线程,还没有调用start()
- Runnable: 就绪状态,已经调用start(), 在抢夺和抢夺后执行中都是Runnable状态
- Blocked: 阻塞状态,当线程需要的锁被其他线程抢了, 进入阻塞状态,不会再抢cpu执行权, 当锁被释放,所有处于等待这把锁的线程都会变成就绪状态, 重新争夺锁和cpu执行权
- Waiting: 无限等待, 直到被唤醒进入就绪状态,进入无限等待的线程会释放cpu执行权,也释放锁
- Timed Waiting:计时等待, 计时结束进入就绪状态, 进入计时等待的线程会释放cpu执行权,但不释放锁
- Terminated: 终止状态,等待回收
线程池
通过使用线程池中的线程, 大大降低了创建销毁线程所浪费的资源, 当我们需要一个线程时, 从线程池中取出, 用完再放回, 提高代码复用性
创建线程池的两种方式
1.使用Executors工具类的方法创建线程:
public ExecutorService newFixedThreadPool(int n): 创建一个指定线程数可重用的线程池
public class ThreadPoolDemo {
public static void main(String[] args) {
// 使用工具类创建线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
// 循环使用线程中的线程执行线程任务
pool.submit(()->System.out.println(Thread.currentThread().getName()+"线程池中的线程执行了!"));
}
//线程池中需要手动停止 不然会一直运行等待接收线程任务
pool.shutdown();
}
}
2.自定义线程池通过创建ThreadPoolExecutor对象(7个参数)
1. 核心线程数 (CPU密集型: 建议核心数+1; IO密集型: 建议核心数*2)
2. 最大线程数 (核心线程数+临时线程数= 总和)
3. 临时线程存活时间 (存活具体数值)
4. 临时线程存活时间 (存活单位 eg: TimeUnit.SECONDS)
5. 阻塞队列 (有界阻塞队列 / 无界阻塞队列)
6. 线程工厂 (通过使用Executor的静态方法defaultThreadFactory获得线程工厂)
7. 拒绝策略 (AbortPolicy / DiscardPolicy / DiscardOldPolicy / CallerRunsPolicy)
public class ThreadPoolDemo2 {
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor pool = new ThreadPoolExecutor(5,10,30, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardPolicy());
for (int i = 0; i < 10; i++) {
pool.submit(()-> System.out.println(Thread.currentThread().getName()+"执行了"));
Thread.sleep(200);
}
pool.shutdown();
}
}