多线程
1、并发与并行
并发:指两个/多个事件在同一时间段发生(交替进行)
并行:指两个/多个事件在同一时刻发生(同时发生,同时进行)
2、进程和线程
硬盘:永久存储ROM
内存:临时存储RAM
- 所有应用程序都要进入内存执行,进入到内存的程序叫进程
- 一个程序至少有一个进程,一个进程可以包含多个线程
- 线程属于进程,是进程中的一个执行单元,负责程序执行
- 单核心单线程cpu:在多个线程间做高速切换,效率低
- 4核心8线程cpu:可同时执行8个线程,在多个任务间高速切换,是单核cpu速度的8倍
3、线程调度
- 分时调度:所有线程轮流使用cpu,平均分配时间
- 抢占式调度:优先让优先级高的线程使用CPU,优先级相同时,会随机选择一个。(任务管理器可设置优先级)
4、创建线程类
主线程:执行主(main)方法的线程
单线程程序:只有一个线程,执行从main开始,从上到下依次执行。
JVM执行main方法,main方法会进入到栈内存
JVM会找操作系统开辟一条main方法通向CPU的执行路径,cpu就可以通过这个路径来执行main方法
创建新线程的两种方法:
- 将类声明为Thread的子类,该子类应重写Thread的run方法。创建实例并调用start方法->run
public class ThreadTest extends Thread{
public void run() {
System.out.println("创建了线程");
}
public static void main(String[] args) {
ThreadTest t=new ThreadTest();
t.start();
}
}
- 声明实现Runnable的类,然后实现run方法,并分配该类实例,在创建Thread时作为一个参数传递并启动。new Thread®
public class Runnable01 implements Runnable{
public void run() {
System.out.println(Thread.currentThread().getName()+"建立了新线程");
}
}
public class DemoRunnable {
public static void main(String[] args) {
//创建Runnable接口的实现类对象
Runnable01 r=new Runnable01();
//创建新线程,传入Runnable接口的实现类
Thread t=new Thread(r);
//调用start方法开启新线程,实现新方法
t.start();
}
}
- 多线程内存:start()方法执行时,会开辟新的栈空间来执行run()方法
- 好处:多个线程间互不影响(在不同的栈空间)
- 实现Runnable接口创建多线程程序的好处:
1、避免了程序的单继承性;实现该接口后,还能继承其他的类或实现其它接口。
2、增强了程序的扩展性(降低耦合性),把设置线程任务(run())和开启新线程(start())进行分离。
5、匿名类方式实现线程的创建
作用:简化代码
//使用匿名内部类的方法,实现多线程
new Thread(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+"建立了新线程");
}
}).start();
使用lambda表达式简化
//使用lambda表达式
new Thread(()-> {
System.out.println(Thread.currentThread().getName()+"建立了新线程");
}).start();
线程安全
1、线程安全
多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。
2、解决线程安全问题
为保证每个线程都能正常执行原子操作,Java引入了线程同步机制:
- 同步代码块
- 同步方法
- 锁机制
3、同步代码块
- 格式:
synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
- 注意事项
- 代码块中的锁对象,可使用任意的对象
- 但是必须保证多个线程使用的锁对象是同一个
- 锁对象作用:把同步代码块锁住,只让一个线程在其中执行
- 同步技术的原理:使用了一个锁对象,这个锁对象叫同步锁,也叫对象锁,也叫对象监视器。
比如t0抢到cpu执行权,执行run()方法时,遇到synchronized代码块会检查是否有锁对象,若有,就会获取锁对象,进入到同步中执行。 t1抢到cpu执行权时,执行run()方法,遇到了synchronized代码块会检查是否有锁对象,发现没有,t1会进入阻塞状态,直到t0执行完同步中的代码,将锁对象归还给同步代码块时t1才能获取锁对象进入到同步中执行。 - 总结:同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁进不去同步。
4、使用同步方法
- 把访问了共享数据的代码抽取出来,放到一个方法中
- 在方法上添加synchronized修饰符
- 格式
修饰符 synchronized 返回值类型 方法名(参数列表){
可能会出现线程安全问题的代码
}
- 同步方法的锁对象是实现类对象,也就是this。
- 静态的同步方法(static):锁对象是本类的class属性,class文件对象(反射),不是this,因为静态方法优于对象,this是创建对象后产生的。
5、Lock锁
Lock接口实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作
- 在成员位置创建一个Reentrantlock对象
- 在可能出现安全问题的代码前调用lock获取锁
- 在可能出现安全问题的代码后调用unlock释放锁
线程状态:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OZpoTPBz-1575130359472)(“C:\Users\11974\Desktop\笔记截图\线程状态.png”)]
进入计时等待(Time-Waiting)两种方式:
- 使用sleep(long m):在毫秒值结束之后,会进入Runnable/Blocked状态
- 使用wait(long m):在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,进入到Runnable/Blocked状态
- notify():如果有多个线程任意唤醒一个
- notifyAll():唤醒所有等待的线程
等待唤醒机制
1、线程间通信
概念:多个线程在处理同一个资源,但处理的动作(线程任务)不同
2、等待唤醒机制
概念:多个线程间的一种协作机制
方法:wait()、notify()、notifyAll()
线程池
1、概念
容器–>集合(ArrayList、HashSet、LinkedList、HashMap
使用remove()和add()(以及类似方法)来使用或归还线程
在JDK1.5后,内置了线程池,避免频繁创建销毁线程。
2、优点
合理利用线程池的好处:
- 降低资源消耗,减少创建和销毁的次数,每个工作线程都可重复利用,可执行多个任务。
- 提高响应速度,任务到达时不需等待线程创建。
- 提高线程的可管理性,可根据任务数量调整池中工作线程的数目,防止因消耗过多内存使服务器崩溃。
Lambda表达式
1、函数式编程思想概述
函数式编程会尽量忽略面向对象的复杂语法-强调做什么,而不是以什么形式做。
2、冗余的runnbale代码
- 需要Runnable接口
- 创建实现了该接口的实现类
- 为省去定义一个实现类的麻烦,不得不使用匿名内部类
- 必须覆盖抽象run方法
- 但事实上只有方法体才是关键所在
3、Lambda表达式标准格式
适用于需要实现接口的类,同时需要重写的方法只有一个
一些参数+一个箭头+一段代码
(参数列表)->{重写方法的代码}
Lambda表达式:
- 使用Lambda必须有接口,且接口中有且只有一个抽象方法
- 使用Lambda必须具有上下文推断,有且只有一个抽象方法的接口,称为函数式接口。