1.线程/进程
进程: 在操作系统中能够独立运行,并且作为资源分配的基本单位。它表示运行中的程序。系统运行一个程序就是一个进程从创建、运行到消亡的过程。
线程:是一个比进程更小的执行单位,能够完成进程中的一个功能,也被称为轻量级进程。一个进程在其执行的过程中可以产生多个线程。
2.线程的状态
1.新建状态:新创建一个线程对象(比如 new thread())
2.就绪状态:调用了线程对象的start()方法就会进入就绪状态,但不意味着立即执行,还需要等待cpu
分配时间
3.运行状态: 获得了cpu使用权,进入运行状态,执行线程体的代码块.
4.阻塞状态: 由于某种原因导致线程失去了cpu的使用权,暂时停止运行,比如wait(),sleep();
5.死亡状态:线程执行完毕或因为异常推出了run()方法,该线程的生命周期结束.
状态转化图:
3.多线程的实现方式
1.继承Thread 重写run()方法
/**
* 线程对象直接继承Thread 重写run方法
*
* @author Administrator
*/
public class ThreadDemo extends Thread {
@Override
public void run() {
// 线程任务
System.out.println("我是继承Thread的线程");
}
/**
* 测试使用线程
* @param args
*/
public static void main(String[] args) {
ThreadDemo thread = new ThreadDemo();
thread.start();
}
}
2.实现runnable 重写run()方法
/**
* 实现runnable接口 重写run方法
*/
class RunnableDemo implements Runnable {
@Override
public void run() {
// 线程任务
System.out.println("我是实现runnable接口的线程");
}
/**
* 测试使用线程
* @param args
*/
public static void main(String[] args) {
RunnableDemo runnableDemo = new RunnableDemo();
Thread thread2 = new Thread(runnableDemo, "runnable");
thread2.start();
}
}
3.实现callable重写call()方法
/**
* 实现callable接口 从写call方法 可以有返回值
*/
class CallableDemo implements Callable<String> {
@Override
public String call(){
// 线程任务
return "我是实现callable接口的线程";
}
/**
* 测试使用线程
* @param args
*/
public static void main(String[] args) {
CallableDemo callableDemo = new CallableDemo();
FutureTask<String> ft = new FutureTask<>(callableDemo);
Thread thread3 = new Thread(ft, "callable");
thread3.start();
String integer1 = ft.get();
System.out.println(integer1);
}
}
为什么我们不直接run()方法,而是要调用线程的start()方法
调用Thread的start方法可以启动线程,并使线程进入就绪状态,而run方法只是线程对象的普通方法,还是在主线程上执行
4.线程池的配置
1.spring线程池 ThreadPoolTaskExecutor
/**
* springboot 线程池配置
*
* @author Administrator
*/
@Configuration
public class ThreadPoolTaskExecutorConfig {
@Bean
public TaskExecutor taskExecutor() {
// spring 线程池配置
// 核心配置
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
// 核心线程数
taskExecutor.setCorePoolSize(3);
// 最大线程数
taskExecutor.setMaxPoolSize(10);
// 设置队列大小
taskExecutor.setQueueCapacity(Integer.MAX_VALUE);
// 非核心线程的最大空闲时间(超过这个时间的空闲的非核心线程将被销毁) 单位s 秒
taskExecutor.setKeepAliveSeconds(100);
// 配置拒绝策略
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
// 初始化线程池
taskExecutor.initialize();
return taskExecutor;
}
}
2.jdk 自带的线程池 ThreadPoolExecutor
/**
* juc 线程池配置(jdk自带)
*
* @author Administrator
*/
@Configuration
public class ThreadPoolExecutorConfig {
@Bean
public ExecutorService taskExecutor() {
// juc线程池配置(JDK)
// int corePoolSize, 核心线程
// int maximumPoolSize, 最大线程
// long keepAliveTime, 非核心线程最大等待时间
// TimeUnit unit, 参数的时间单位
// BlockingQueue<Runnable> workQueue, 任务队列
// ThreadFactory threadFactory, 创建线程的工厂类
// RejectedExecutionHandler handler 拒绝策略
ThreadPoolExecutor executor = new ThreadPoolExecutor(
3,
10,
1000L,
TimeUnit.SECONDS,
new LinkedBlockingQueue(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
return executor;
}
}
线程池的执行流程:
任务被提交到线程池,先判断当前线程是否小于corePoolSize(核心线程数),如果小于,则创建新的线程来执行提交的任务. 否则将任务放入workQueue队列,如果队列满,则判断当前线程数是否小于最大线程数MaximumPoolSize,如果小于,则创建线程执行任务,否则就会根据拒绝策略,处理超出容量的任务.
使用线程池的好处:
提高线程的利用率
提高程序的响应速度
便于统一管理
可以控制最大并发数
5.多线程会产生的问题以及如何避免
多线程主要会带来以下两个问题:
线程安全问题
一个线程从开始到结束,其中的某一个时间,这个线程操作的数据被别的线程修改,对于当前线程而言,数据可能是错误的,这就是发生了线程安全问题.
发生线程安全问题的原因:
1.存在多线程.
2.存在共享资源,且多线程操作该共享资源.
3.对共享资源有非原子性操作.
如何避免:
1.尽量不是用共享变量,将不惜要的共享变量换成局部变量.
2.加锁,使用synchronized关键字或者lock锁操作
3.使用ThreadLocal为每一个线程创建一个变量副本,各个线程之间独立操作,互不影响.
性能问题
产生的原因:
1.线程的生命周期开销是非常大的,一个线程的创建到销毁会占用大量的内存,
2.cpu在给每个线程分配时间片,需要不断的切换线程执行,这个切换的过程也比较耗时.
3.线程创建的数量不合理,也会导致性能问题,如果存在过多的线程闲置,闲置的内存会占用大量的内存
解决思路:
使用线程池来创建和管理线程
6.锁
synchronized 隐式锁:java中的一个关键字,它可以修饰实例方法,静态方法,以及代码块
修饰普通的同步方法: 锁是当前的实例对象
修饰静态的同步方法: 锁的是当前类的Class对象
修饰代码块:锁的是synchronized括号里面配置的对象
关于锁的释放: 得到锁的线程执行完同步代码块,或者发生了异常 jvm自动释放
Lock 显示锁:是jdk提供的一个接口 它只能修饰代码块
Lock需要手动的加锁与释放锁
区别
公平锁:遵循FIFO原则 (先入先出原则)
非公平锁:锁一旦释放,大家一起竞争.先来的线程不一定能拿到锁
volatile关键字 只能修饰变量,是线程同步的轻量级实现,被volatile修饰的变量,每次被使用都要到主存中进行读取,
7.多线程的实际应用
1.配置线程池 上面已说明配置方案
2.在需要使用多线程的service中注入.
@Slf4j
@Service
public class WsSupplyServiceImpl implements IWsSupplyService {
@Resource
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
public void test() {
// 开启线程执行任务
threadPoolTaskExecutor.execute(()->{
// 线程任务 无返回值
});
Future<?> submit = threadPoolTaskExecutor.submit(() -> {
// 线程任务 有返回值
return "";
});
try {
// 获取返回值
Object o = submit.get();
}catch (Exception e) {
e.printStackTrace();
}
// 等待全部有返回的线程任务执行完成 可以传多个Future
CompletableFuture.allOf(submit);
}
}
在springboot中的应用
1.在线程池配置类添加注解@EnableAsync
2.在需要异步执行的方法上添加注解@Async("配置的bean的名称")
死锁: 线程一直等待某个资源被释放,这种一直等待的状态就是死锁.
如何避免死锁:
1.避免一个线程同时获取多个锁
2.避免一个线程在锁内同时占有多个资源
3.尝试使用定时锁.使用lock.tryLock(timeOut)
sleep()与wait()的区别
相同点: 都可以暂停线程的执行,进入等待状态
不同点: sleep不会释放锁,wait会释放锁
sleep属于Thread类的静态方法,wait是Object的实例方法 作用域对象本身
执行sleep()后 可以调用Interrupt()方法唤醒休眠中的线程,而执行wait()需要通过notify()或者notifyall()方法唤醒.