- 线程生命周期
1.NEW:新建一个线程
2.READY:调用start方法开始运行,线程处于Ready(可运行)状态。可运行状态获取了CPU时间片之后处于Running(运行)状态
3.WAITING:当线程调用wait方法后,进入Waiting(等待)状态,进入等待状态的线程需要其他线程的通知才能够返回到运行状态
4.TIME_WAITING:与Waiting类似,但是当超时时间到达后线程将返回到Running状态。
5.BLOCKED:当线程调用同步方法时,,在没有获取到锁的情况下,线程会进入到BLOCKED(阻塞)状态
6.TERMINATED:线程执行完run方法后,会进入到TERMINATED(终止)状态
- 什么是死锁?
描述:多个线程同时被阻塞,它们中的⼀个或者全部都在等待某个资源被释
放。由于线程被⽆限期地阻塞,因此程序不可能正常终⽌
- 如何避免死锁
通过加锁来避免死锁。加锁的方式:
1.synchronized
2.ReentrantLock
- synchronized 关键字
解决多个线程访问资源的同步性,可以保证被synchronized 修饰的部分在任意时刻只有一个线程执行。
三种使用方式:
1)修饰实例⽅法:作⽤于当前对象实例加锁,进⼊同步代码前要获得当前对象实例的锁
2)修饰静态⽅法:给当前类加锁,会作⽤于类的所有对象实例
3)修饰代码块:指定加锁对象,对给定对象加锁,进⼊同步代码库前要获得给定对象的锁
单例模式
public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public synchronized static Singleton getInstance() {
//先判断对象是否已经实例过,没有实例化过才进⼊加锁代码
if (instance == null) {
//类对象加锁
synchronized (Singleton.class) {
//防止指令重排导致先分配内存,造成异常
if (instance== null) {
instance = new Singleton();
}
}
}
return instance;
}
}
instance 采⽤ volatile 关键字修饰, instance = new Singleton(); 这段代码其实是分为三步执⾏:
- 为 instance 分配内存空间
- 初始化 instance
- 将 instance 指向分配的内存地址
由于 JVM 具有指令重排的特性,执⾏顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致⼀个线程获得还没有初始化的实例。
例如,线程 T1 执⾏了 1 和 3,此时 T2 调⽤ getUniqueInstance() 后发现 instance 不为空,因此返回instance ,但此时 instance 还未被初始化。
使⽤ volatile 可以禁⽌ JVM 的指令重排,保证在多线程环境下也能正常运⾏。
- ReentrantLock
1.ReentrantLock实现是通过利用CAS(CompareAndSwap)自旋机制保证线程操作的原子性和volatile保证数据可见性以实现锁的功能。
2.ReentrantLock则需要用户去手动释放锁,如果没有手动释放锁,就可能导致死锁现象。一般通过lock()和unlock()方法配合try/finally语句块来完成
3.ReentrantLock可以中断,可通过trylock(long timeout,TimeUnit unit)设置超时方法或者将lockInterruptibly()放到代码块中,调用interrupt方法进行中断
4.可以设置是否为公平锁,通过构造方法new ReentrantLock时传入boolean值进行选择,为空默认false非公平锁,true为公平锁
5.ReentrantLock通过绑定Condition结合await()/singal()方法实现线程的精确唤醒
6.synchronzied锁的是对象,锁是保存在对象头里面的,根据对象头数据来标识是否有线程获得锁/争抢锁;ReentrantLock锁的是线程,根据进入的线程和int类型的state标识锁的获得/争抢。