线程安全问题和解决方案
线程安全的概念
一.概念: 多个线程同时访问一个对象,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。
线程安全问题产生的原因
1.抢占式执行(线程不安全的罪魁祸首,万恶之源)。 多个线程的调度执行,可以视为是"随机的",虽然有调度算法,但是在应用程序上来看确实没有规律可言。所以我们在写多线程的时候,尽量保证在任何一种调度的情况下都能有正确的运行结果。
2.多个线程修改同一个变量。 注意:一个线程修改一个变量,没事,多个线程读取一个一变量,没事,多个线程修改不同的变量也没事。
3.修改操作不是原子的。 如果某些修改操作不是一条指令就能完成的,而是cup执行多条指令。(这里指令就是cpu上最小的单元),就会导致cpu执行到某个指令时,就被调度走,去执行其他的指令,从而导致道歉的操作未能及时完成,从而对运算结果造成了影响,故线程不安全。
4.内存可见性问题。 可见性是指,当多个线程并发访问共享变量时,一 个线程对共享变量的修改,其它线程能够立即看到。 一般来说cpu从主内存中读取的效率不高,一般中间都会有几级缓存。每个线程读取共享的变量时,都会将变量加载到cpu的高速缓存中,修改变量后,cpu会立即更新该缓存,但是并不一定会重新把它写会主内存(写会内存的时间不知道),而这个时候,其他的线程(尤其是不在同一个cpu上执行的线程)访问这个变量时,读取到的就是就得的数据,而不是更新后的数据。
5.指令的重排序问题 可能编写的代码,不会按照顺序执行,JVM为了提高程序的整体效率可能会对代码进行优化,其中就有对代码顺序进行优化。
线程安全的反制,解决方案
一般对于线程安全问题,都是针对上面第三点,把修改操作打包后变成原子性的操作指令——加锁。 如果类似修改操作在加锁和解锁之间进行,那么其他线程也想要加锁就只能阻塞等待,进入BLOCKED状态,直到之前加锁的线程解锁了,其他线程才能加锁,所以只有针对同一个多对象,才存在锁竞争,不同的线程针对两个不同的对象加锁,是不会产生锁竞争的,各自执行各自的,不会存在阻塞状态。(注意: 在使用锁的时候,一定要明确当前是针对哪个对象进行加锁)。
使用关键字:synchronized(对象引用:比如this) 只能锁一个对象
1.可以修饰普通方法: 锁的是当前实例对象
2.可以修饰代码块: 锁的是synchronized括号里的对象
3.可以修饰静态方法: 锁的是当前类的class对象(该类所有对象共享)
注意: 任何对象(成员变量,局部变量,静态变量,类对象)都可以在synchronized里面作为对象,我们不关心所的对象究竟是谁,是什么形态,只关心是不是多个线程锁的是同一个对象,锁同一个对象有锁竞争,锁不同的对象没有。