多线程启动方式
- 实现一个类,继承Thread,重写run方法。(具名类or匿名类)
- 实现Runnable接口,重写run方法。(具名类or匿名类)搭配Thread实例
- 使用lambda表达式,搭配Thread实例
- 实现Callable接口,实现call方法,使用FutureTask包装,搭配Thread实例。【把线程封装成带返回值的了】
静态方法和实例方法的区别
- 调用静态方法的代码行处于哪个线程中,就是获取哪个线程的属性
run方法和start方法
- run方法直接调用,不会启动线程,只是在当前main线程中,调用run方法
- 线程启动是通过start方法启动
JAVA进程的退出
- 至少有一个非守护线程没有被销毁,进程就不会退出
- 非守护线程一般可以称为工作线程,守护线程可以称为后台线程
- setDaemon(true) 设置守护线程
使用多线程提高效率需要考虑的因素
- 所有线程执行是并发+并行
- 线程创建,销毁比较费时
- 线程的调度由系统决定(线程越多,系统调度越频繁,线程就绪态转变为运行态,也是有性能及时间消耗)
- 单个线程的任务量
join
- 当前线程,代码执行的时候所在的线程
- t线程:线程引用对象
- 当前线程进行阻塞(运行态—>阻塞态)等待(满足一定条件),t线程(不做任何处理,让t执行运行)
- 一定条件是什么:以下条件哪个先执行完,就满足
- 传入的时间(时间值,单位是毫秒)
- 线程引用对象执行完毕
线程的中断相关api
- boolean isInterrupted() 测试这个线程是否被中断
- void interrupt() 中断这个线程
- static boolean interrupted() 测试当前线程是否中断 //返回当前中断标志 位,并且重置中断标志位
Interrupted
- 线程调用wait()/join()/sleep()阻塞时,如果把当前线程给中断,会直接抛一个异常
- 线程运行状态时,需要自行判断线程中断标志位,处理中断操作位。
- 阻塞状态时,通过捕获及处理异常,来中断线程的逻辑
- 抛出异常后,线程中断标志位会进行重置
线程的真实中断方法:过期方法stop()
- 线程启动以后:有一个中断标志位=false
- 在线程运行态中,处理线程中断,需要自行通过判断中断标志位,来进行中断的逻辑处理逻辑(Thread.isInterrupted()/Thread.interrupted)
- 线程因调用wait()/join()/sleep()处于阻塞状态时,将线程中断,会造成:
- 在这三个阻塞方法所在的代码行,直接抛出InterruptedException异常
- 抛出异常之后,重置线程中的中断标志位=true
自定义中断标志位
- (缺点)满足不了线程处于阻塞状态时,中断操作
多线程安全(重点)
- 原子性
- 特殊的原子性代码(分解执行存在编译为class文件时,也可能存在cpu执行指令)
- n++ n-- ++n --n 都不是原子性:
- 需要分解为三条指令: 从内存读取变量到cpu,修改变量,写回内存
- 对象的new 操作
- Object obj=new Object();
- 分解为三条指令:分配对象的内存、初始化对象、将对象赋值给变量
- Object obj=new Object();
- n++ n-- ++n --n 都不是原子性:
- 可见性
-
主内存,工作内存
- 代码顺序性
多线程操作考虑
- 安全
- 效率
- 在保证安全的前提条件下,尽可能的提高效率:
- 代码执行时间比较长,考虑多线程(线程 的创建,销毁的时间消耗)
- 如果不能保证安全,所有代码都没有意义——先保证安全,再保证效率。
- 在保证安全的前提条件下,尽可能的提高效率:
明确锁的对象
-
实例方法
-
public class SynchronizedDemo { public synchronized void methond() { } public static void main(String[] args) { SynchronizedDemo demo = new SynchronizedDemo(); demo.method(); // 进入方法会锁 demo 指向对象中的锁;出方法会释放 demo 指向的对象中的锁 } }
-
-
静态方法
-
public class SynchronizedDemo { public synchronized static void methond() { } public static void main(String[] args) { method(); // 进入方法会锁 SynchronizedDemo.class 指向对象中的锁;出方法会释放SynchronizedDemo.class 指向的对象中的锁 } }
-
-
代码块
-
public class SynchronizedDemo { public void methond() { // 进入代码块会锁 this 指向对象中的锁;出代码块会释放 this 指向的对象中的锁 synchronized (this) { //TODO } } public static void main(String[] args) { SynchronizedDemo demo = new SynchronizedDemo(); demo.method(); } }
-
-
进入synchronized代码行时,需要获取对象锁:
- 获取成功:往下执行代码
- 获取失败:阻塞在synchronized代码行
-
退出synchronized代码块,或synchronized方法:
- 退回对象锁
- 通知JVM及系统,其他线程可以来竞争这把锁
-
synchronized加锁操作的关注点:
-
对哪一个对象进行加锁 ——————(一个对象只有一把锁)
-
只有同一个对象,才会有同步互斥的作用(多线程线程安全的三大特性都能够满足)
-
对于synchronized内的代码来说,在同一个时间点,只有一个线程在运行(没有并发,并行)。
-
运行的线程数量越多,性能下降越快(归还对象锁的时候,就有越多的线程不停的在被唤醒、阻塞状态之间切换)
-
同步代码块执行时间长,线程数量少,那效率就越高,反之,同步代码块执行时间短,线程数量多,那效率就越低。
-
Volatile关键字
- 保证可见性
- 保证有序性
- 不能保证原子性
- volatile修饰的变量,进行赋值不能依赖变量(常量赋值可以保证线程安全)
- 使用场景
- volatile可以结合线程加锁的一些手段,提高线程效率
- 只是变量的读取、常量赋值、可以不加锁。而是使用volatile,可以提高效率。
-
原子性缺失例子
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u5LiWjK9-1601630928604)(多线程.assets/多线程-volatile-原子性缺失.png)]
双重校验锁
public class Sington4 {
private static volatile Sington4 sington4 = null;
//提高效率:变量使用volatile可以保证其可见性
private Sington4() {
}
public static Sington4 getInstance() {
if (sington4 == null) {
synchronized (Sington4.class) {
if (sington4 == null) {
// new 对象分解为三条指令:前两个指令是new,第三个是=
//1.分配内存空间
//2.初始化对象
//3.赋值给变量
sington4 = new Sington4();
}
}
}
return sington4;
}
}
线程间的通信
为什么使用多线程
-
PU执行指令
- 计算指令
- 逻辑指令
- 数据拷贝
-
某一个JAVA进程,程序可能偏向
- 计算密集型的任务
- IO密集型的任务
-
执行比较耗时的操作时,使用多线程
-
执行阻塞式代码,会对当前线程造成阻塞时,