线程安全问题
线程安全的意思,是在多线程各种随机的调度顺序下,代码都没有bug,都能够符合预期的方式来执行
如果在多线程各种随机的调度顺序下,代码出现bug,此时就认为是线程不安全的
线程不安全的原因:
1、抢占式执行,多个线程的执行调度过程,可以视为是没有规律的随机的
2、多个线程同时修改同一个变量
3、修改操作不是原子的---原子表示不可分割的最小单位
4、内存可见性问题---JVM的代码优化引入的bug
5、指令重排序---如果在单线程情况下,JVM、CPU指令集会对指令进行优化,按照一个优化的顺序去调度执行
解决线程安全问题需要使用到synchronized关键字
synchronized关键字
synchronized 会起到互斥效果,某个线程执行到某个对象的 synchronized 中时,其他线程如果也执行到同一个对象 synchronized 就会阻塞等待
在java中,任意的对象,都可以作为锁对象,在使用锁的时候,一定要明确是针对哪个对象加锁,直接影响到后面锁操作是否会触发阻塞
synchronized这个操作将"并发"变成了"串行",确实会减慢执行效率
进入 synchronized 修饰的代码块, 相当于 加锁,加锁期间其他线程执行到这里会线程阻塞
退出 synchronized 修饰的代码块, 相当于 解锁,线程阻塞的线程开始加锁执行此代码块
synchronized修饰方法(修饰方法相当于锁对象就是this):
修饰符 synchronized 返回值类型 方法名(参数) {
}
synchronized修饰代码块(如果一个方法中有些代码需要加锁有些不需要,就可以使用代码块):
synchronized (任意对象,例如this) {
}
如果对一个线程的同一个代码区域重复加锁(加了两把锁,方法一把,代码块一把),就有可能会出现死锁的现象,因为第二把锁永远也等不到第一把锁执行结束(解锁)
针对上述情况,不会产生死锁的话,这样的锁就称之为"可重入锁",会产生死锁的话,这样的锁就称之为"不可重入锁"
synchronized关键字是可重入的(也就是如果重复加锁,重复的锁会被忽略掉),这是因为其内部维护了一个计数器,来衡量啥时候是真加锁/解锁,啥时候是重复的锁需要被忽略掉
synchronized关键字是一个非常优秀的设计,无论是正常执行完代码块还是触发异常,都会触发解锁操作