目录
多线程
在同一时刻,只能有一个程序在执行,感觉这些程序你是在同时进行,实际上是CPU在高效的切换。
进程
就是正在运行的程序,也就是代表了程序锁占用的内存区域。
- 独立性:
- 进程是系统中独立存在的实体,每个进程都有自己私有的地址空间。
- 在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间。
- 动态性:
- 进程与程序的区别在于程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。
- 在进程中加入了时间的概念,进程具有自己的生命周期和各种不同的状态。
- 并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。
线程
- 线程(thread)是操作系统能够进行运算调试的最小单位。
- 它被包含在进程之中,是进程中的实际运作单位。一个进程可以开启多个线程。
- 一个程序运行后至少有一个进程,一个进程里包含多个线程。
- 单线程:进程只有一个线程。
- 多线程:进程中有多条执行路径。
多线程生命周期
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tpa59d3g-1609346543569)(D:\0000note\1基础note\img\多线程生命周期.png)]
线程生命周期,总共有五种状态:
-
新建状态:
当线程对象创建后,即进入了新建状态
Thread t = new MyThread();
-
就绪状态:(New)
- 当调用线程对象的start()方法,线程即进入就绪状态。
- 处于就绪状态的线程,只是说明此线程已经做好了被CPU调度执行的准备,并不会立即执行。
-
运行状态:(Runnable)
- 当 CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行。
- 就绪状态是进入到运行状态的唯一入口,也就是说线程要想进入运行状态,首先必须处于就绪状态中。
-
阻塞状态:(Blocked)
- 处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。
- 阻塞状态分三种:
- 等待阻塞:运行状态中的线程执行wait() 方法,使本线程进入到等待阻塞状态。
- 同步阻塞:线程在获取synchronixed同步锁失败(被其它线程占用),它会进入到同步阻塞状态。
- 其他阻塞:
- 通过调用线程的sleep() 或join()或发出了I/O请求时,线程会进入到阻塞状态。
- 当sleep()状态超时、join()等待线程终止或超时、I/O处理完毕时线程重新转入就绪状态。
-
死亡状态:(Dead)
- 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
多线程创建
继承Thread
- Thread本质上是实现了Runnable接口的一个实例,代表一个线程的实例。
- 启动线程的唯一方法就是通过Thread类的start() 实例方法。
- start()方法最终由操作系统启动一个新线程,操作系统将执行run() 方法。
常用方法
方法 | 用处 |
---|---|
String getName() | 返回该线程的名称 |
Static Thread currentThread() | 返回对当前正在执行的线程对象的引用 |
void setName(String name) | 改变线程名称 |
static void sleep(long millis) | 在指定的毫秒内让正在执行的线程休眠 |
void start () | 使该线程开始执行 |
Tread(String name) | 分配新的Thread对象(构造方法) |
实现Runnable接口
若当前类已经ectends另一个类,就无法多再继承Thread类,这里还可以实现Runnable接口。
常用方法
void run()
//当实现接口的对象 Runnable被用来创建一个线程,启动线程使对象的 run在独立执行的线程中调用的方法。
对比
方式 | 优点 | 缺点 |
---|---|---|
Thread | 编写简单,定义了很多操纵线程的方法 | 线程类已经继承了Thread类就不能再继承其他父类了。 |
Runnable | 线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。多个线程可以共享一个Target对象,很适合多个相同线程来处理同一份资源。 | 编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread() 方法。 |
Callable | Runnable规定的方法是run(); Callable规定的方法是call(); Callable的任务执行后可返回值,Runnable不行;call方法可以抛出异常,run方法不行 | 存取其他项慢。 |
Pool | 线程池可以创建固定大小,这样无需反复创建线程对象,比较节省资源。 | 编程繁琐,难以理解! |
同步锁
- 把有可能出现问题的代码包起来,一次只让一个线程执行。通过sychronized关键字实现同步。
- 当多个对象操作共享数据时,可以使用同步锁解决线程安全问题。
synchronized
两种用法:
-
代码块锁
synchronized(对象){ 需要同步的代码; }
-
实例锁:
-
修饰普通方法,在方法返回值前加synchronized。(默认是synchronized(this))
-
修饰static,在方法返回值前加synchronized。(默认是synchronized(className.class))
-
当共享资源是static类型、实例锁修饰的方法是非静态方法时,实例锁此锁无效。要用代码块锁(synchronized(className.class))
class A extends Thread{ static private int i; public void run(){ synchronized(A.class){ sout(i); } } }
-
特点
- 前提
- 同步需要两个或者两个以上的线程。
- 多个线程间必须使用同一个锁。
- 同步的缺点是会降低程序的执行效率,为了保证线程安全,必须牺牲性能。
- 可以修饰方法称为同步方法,使用的锁对象是this。
- 可以修饰代码块称为同步代码块,锁对象可以任意。
单例设计模式(Singleton Pattern)
- 单例模式属于创建型模式,提供了一种创建对象的最佳方式。spring默认创建的bean就是单例模式的。
- 单例模式确保对象只有一个,就是类在内存中的对象只有一个。
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
介绍
-
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
-
一个全局使用的类频繁地创建与销毁。
##### 处理思路
1. 判断系统是否已经有这个单例,有则返回,没有则创建。
2. 关键代码——私有化构造函数。
##### 优点
1. 内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
2. 避免对资源的多重占用。
##### 缺点
没有接口,不能继承,与单一职责原则冲突(一个类应当只关心内部的逻辑,而不关心外面怎么实例化)。
实现——懒汉式(线程不安全)
描述
- 这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。
- 因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。
实例
public class MySingle{
private static MySingle my;
private MySingle(){}
public static MySingle getMy(){
if(my == null) my = new MySingle();
return my;
}
}
实现——懒汉式(线程安全)
描述(延迟加载的思想)
- 这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
- 优点:第一次调用才初始化,避免内存浪费。
- 缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。
public class MySingle{
private static MySingle my;
private MySingle(){}
public static synchronized MySingle getMy(){
if(my == null) my = new MySingle();
return my;
}
}
实现——饿汉式(线程安全)
描述(延迟加载的思想)
-
这种方式比较常用,但容易产生垃圾对象。
-
优点:没有加锁,执行效率会提高。
-
缺点:类加载时就初始化,浪费内存。
-
它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。
public class MySingle{
//创建自身对象
private static MySingle my = new MySingle();
//私有化构造方法
private MySingle(){}
//设置全局访问方法
public static MySingle getMy(){
return my;
}
public void method(){
System.out.pringt("Hello World!");
}
}
public class Demo{
public static void main(String [] args){
MySingle my = MySingle.getMy();
my.method();//输出:Hello World!
}
}
实现——双检锁/双重校验锁(DCL)(延迟加载,线程安全)
描述
这种方式采用双锁机制,安全且在多线程的情况下能保持高性能。
public class MySingle{
private volatile static MySingle my;
private MySingle(){}
public static MySingle getMy(){
if(my == null){
synchronized(MySingle.class){
if(my == null){
my = new MySingle;
}
}
}
return my;
}
}
锁分类
死锁\活锁
公平锁\非公平锁
互斥锁\自旋锁
可重入锁
可中断锁
读写锁
乐观锁\悲观锁
偏向锁\轻量级锁\重量级锁
线程阻塞唤醒
- suspend 和resume
- wait 和 notify
- await 和 singal
- park 和 unpark