目录
多线程存在的问题:竞争条件1
使用多线程可以提高效率,但是会带来其他问题,例如数据共享时出现的混乱问题。
我们考虑这样的代码(check + act)
在单线程程序中运行时,没有任何问题。a和b都是局部变量的时候,也都没有问题。(因为每个线程都会保存一份副本)
我们考虑这样的情形,a和b是成员变量或者静态成员变量,两个线程都来执行这段代码。
假设,线程1执行完判断语句,即将执行赋值语句,好了时间片结束了。这个时候,另一个线程把a的值给改了,当线程1恢复以后,可能不是5了。
多线程存在的问题:竞争条件2
还有可能是(read + modify + write)
假设两个线程同时执行这段代码。
线程1 读取初始值1,然后时间片结束。
线程2 读取初值1,自增,然后存储为2,返回1.
线程1 恢复,自增,然后存储2,返回1.
相当于线程1把线程2的操作给抹掉了(丢失更新)。
多线程存在的问题:数据竞争
我们来看一段初始化的代码
这里对parser进行了验证,如果为空,进行初始化。
当线程1读取为null,他来初始化,完成初始化后,线程2就获取到非空的数据。但是由于没有进行控制,线程1和线程2的写入和读取先后顺序没法保证,很有可能,线程1尚未完成初始化,线程2就读取完了,这样会导致parser初始化两次。
多线程存在的问题:缓存变量
jvm为了性能,对成员变量在各线程存副本,所以,对成员变量操作,很可能其他线程看不到。
这里线程t计算的result,很有可能主线程获取不到,因为result是一个静态成员变量,主线程中存有副本。
使用同步代码
使用synchronize锁定实例方法(使用的是ID的实例):
锁定静态方法(使用的是ID.class):
同步代码段:
线程同步中可能存在的问题
1. 死锁。
线程A等待B持有的资源,同时线程B等待A持有的资源。
两个方法,线程A调用方法1,线程B调用方法2,A线程获取了锁1,等待锁2,B线程获取了锁2,等待锁1,产生了死锁。
2. 活锁。
线程不断地重试一个失败的操作。
3. 饿死。
线程不断的等待资源,老是请求不到资源。例如高优先级的线程始终存在,低优先级的线程可能会饿死。
Synchronize解决的问题
解决的两方面问题:互斥和可见性。
Volatile
volatile解决的是可见性的问题,只解决可见性。
考虑下面的代码:
代码试图通过stopped标志控制线程的while循环,进而停止线程。但是结果不然。
这里涉及到变量缓存的问题,外部主线程更改的stopped状态,thd线程里没有觉察,因为里面还是stopped。
先来看一个错误的写法:
这里使用synchronize实现了同步,但是使用synchronize锁住了一个while循环!
我们使用synchronize是为了解决可见性的问题,也就是说,同步代码更新stopped的状态,但是,线程是先启动的,先获取了锁,然后一直在死循环里,为什么说是死循环?因为锁一致在,stopped始终是false,另一块stopThread方法获取不到锁,改不了stopped的状态。
为了给外部一个改变状态的机会,我们设置一个局部变量,先获取成员变量的副本,再执行打印:
嗯,这次解决了问题,但是,真的需要这样嘛?两个线程并没有并发的写入成员变量,而且,现在是一个线程写,一个线程读,所以同步代码是不必要的。
我们使用volatile来实现:
使用volatile标记的成员变量,各个线程不再保存副本,而是直接读写主内存。
所以调用起来没有问题,可以正常停止。
final和volatile无法共存的问题
使用final标记的不能使用volatile标记。
因为final字段已经保证了可见性,因为它是immunable的,所以无需使用volatile标记。
这个Planets类在构造器初始化Set以后,就不再允许修改内容了,也就是说planets已经属于不可影响的类了。
什么是immunable类
1. 不允许外界修改状态,也就是成员变量、类变量不允许set。
2. 所有的字段都是final
3. 实例的引用不会在构造器中转移。
下面的实例就导致了引用转移: