Java多线程与并发02: Synchronized

多线程存在的问题

 

使用多线程可以提高效率,但是会带来其他问题,例如数据共享时出现的混乱问题。

都有哪些问题呢?

1. 竞争条件

a. 我们考虑这样的代码(check + act)

Java多线程与并发02: Synchronized_第1张图片

当我们在单线程程序中运行时,没有任何问题。

a和b都是局部变量的时候,也都没有问题。(因为每个线程都会保存一份副本)

我们考虑这样的情形,a和b是成员变量或者静态成员变量,两个线程都来执行这段代码。

假设,线程1执行完判断语句,即将执行赋值语句,好了时间片结束了。这个时候,另一个线程把a的值给改了,当线程1恢复以后,可能不是5了。

b. 还有可能是(read + modify + write)

Java多线程与并发02: Synchronized_第2张图片

假设两个线程同时执行这段代码。

线程1 读取初始值1,然后时间片结束。

线程2 读取初值1,自增,然后存储为2,返回1.

线程1 恢复,自增,然后存储2,返回1.

相当于把线程2的操作给抹掉了。

 

2. 数据竞争。

我们来看一段初始化的代码

Java多线程与并发02: Synchronized_第3张图片

这里对parser进行了验证,如果为空,进行初始化。

当线程1读取为null,他来初始化,完成初始化后,线程2就获取到非空的数据,但是由于没有进行控制,线程1和线程2的写入和读取先后顺序没法保证,很有可能,线程1尚未完成初始化,线程2就读取完了,这样会导致parser初始化两次。

 

3. 缓存变量

 

jvm为了性能,对成员变量在各线程存副本,所以,对成员变量操作,很可能其他线程看不到。

Java多线程与并发02: Synchronized_第4张图片

这里线程t计算的result,很有可能主线程获取不到,因为result是一个静态成员变量,主线程中存有副本。

 

使用同步代码

 

使用synchronize锁定实例方法:

使用“this”

Java多线程与并发02: Synchronized_第5张图片

锁定静态方法:

使用ID.class

Java多线程与并发02: Synchronized_第6张图片

同步代码段:

Java多线程与并发02: Synchronized_第7张图片

 

Synchronize解决的问题

 

解决的两方面问题:互斥和可见性。

 

Volatile

 

volatile解决的是可见性的问题,只解决可见性。

考虑下面的代码:

Java多线程与并发02: Synchronized_第8张图片

Java多线程与并发02: Synchronized_第9张图片

代码试图通过stopped标志控制线程的while循环,进而停止线程。但是结果不然。

这里涉及到变量缓存的问题,外部主线程更改的stopped状态,thd线程里没有觉察,因为里面还是stopped。

先来看一个错误的写法:

Java多线程与并发02: Synchronized_第10张图片

这里使用synchronize实现了同步,但是使用synchronize锁住了一个while循环!

Java多线程与并发02: Synchronized_第11张图片

我们使用synchronize是为了解决可见性的问题,也就是说,同步代码更新stopped的状态,但是,线程是先启动的,先获取了锁,然后一直在死循环里,为什么说是死循环?因为锁一致在,stopped始终是false,另一块stopThread方法获取不到锁,改不了stopped的状态。

为了给外部一个改变状态的机会,我们设置一个局部变量,先获取成员变量的副本,再执行打印:

Java多线程与并发02: Synchronized_第12张图片

嗯,这次解决了问题,但是,真的需要这样嘛?两个线程并没有并发的写入成员变量,而且,现在是一个线程写,一个线程读,所以同步代码是不必要的。

我们使用volatile来实现:

Java多线程与并发02: Synchronized_第13张图片

使用volatile标记的成员变量,各个线程不再保存副本,而是直接读写主内存。

所以调用起来没有问题,可以正常停止。

Java多线程与并发02: Synchronized_第14张图片

 

final和volatile无法共存的问题

 

使用final标记的不能使用volatile标记。

因为final字段已经保证了可见性,因为它是immunable的,所以无需使用volatile标记。

Java多线程与并发02: Synchronized_第15张图片

Java多线程与并发02: Synchronized_第16张图片

这个Planets类在构造器初始化Set以后,就不再允许修改内容了,也就是说planets已经属于不可影响的类了。

 

什么是immunable类

 

1. 不允许外界修改状态,也就是成员变量、类变量不允许set。

2. 所有的字段都是final

3. 实例的引用不会在构造器中转移。

下面的实例就导致了引用转移:

Java多线程与并发02: Synchronized_第17张图片

 

你可能感兴趣的:(多线程与并发)