Java并发学习笔记(六)-互斥性和内存可见性

我们都知道,进程是个自封闭的运行环境,它有自己完整的一套运行时资源,特别是有自己的内存地址空间。进程中的线程共享进程的资源,如内存地址空间,文件句柄等。但线程又有自己的计数器、栈、本地变量,现代操作系统大多以线程,而非进程,作为基本调度单元。在大多平台中,JVM以单进程方式执行,Java线程共享JVM进程的内存和文件句柄。在CPU环境下,多线程可以提高CPU的利用率。而在单CPU下,可以在发生I/O的时候继续计算。

但是多线程下,可能发生安全性问题。那怎么解决安全性问题?ps,还有活跃性问题,这个问题还不太明白。

一,互斥性

先来看一下例子

public class UnSafeState {private int val;public int getNext(){return val++;}}val++是个复合操作,在两线程访问时,会发生什么问题?

A线程: val=5 ——> 5+1=6 ——–> val=6B线程:val=5 ——-> 5+1=6 ——> val=6两条线程获取到的val都是6,结果是不正确的。

线程安全性问题的根源在于,多线程共享JVM进程的内存地址空间。当多个线程访问某个类的对象时,对象始终都能表现出正确的行为,称这个类是线程安全的。对象的状态指存储在变量中的数据。对象安全是指对象在多线程下状态是正确的。对象分为有状态和无状态的。

1. 无状态对象一定是线程安全的

public class StatelessValue {public int ran10(){return new Random().nextInt(10);}}该类没有实例变量,也就是对象是无状态的,多线程时对象没有状态可以改变,线程安全。

2. 对象的状态不可变

public class UnchangableValue{private final double PI = 3.14;public double roundArea(double r){return PI*r*r;}}对象的唯一状态PI是个不可变常量,多线程下也无法改变这个状态,线程安全。

3. 单状态可变

状态可变主要问题在于竞态条件,即读取-修改-写入这三步,他们都不是原子性的。

class UnSafeCount {private long count = 0;public long getCount(){return count;}public void service(){System.out.println("I do service for you");count++;}}

这个例子里两个线程操作,可能由于线程执行顺序的不同,结果错误。我们可以使用原子类和内置锁来保证线程安全

内置锁保证线程安全

public class SafeCount {private long count = 0;public synchronized long getCount(){return count;}public void service(){System.out.println("I do service for you");synchronized(this){count++;}}}原子类保证线程安全

public class SafeCount {private AtomicLong count = new AtomicLong(0);public long getCount(){return count.get();}public void service(){System.out.println("I do service for you");count.incrementAndGet();}}另外一个例子就是单例模式

public class UnsafeSingleton{private static UnsafeSingleton instance = null;private UnsafeSingleton(){}public static UnsafeSingleton getInstance(){if(null == instance){instance = new UnsafeSingleton();}return instance;}}线程A判断instance是null,进入if条件语句,此时时间片切换到线程B,,B同样判断instance为null,也进入条件语句,这种情况下,线程A和线程B分别创建了一个对象,不再满足单例模式。要做到线程安全,必须对instance加锁。

public class SafeSingleton{private static SafeSingleton instance = null;private SafeSingleton(){}public static SafeSingleton getInstance(){synchronized(instance){if(null == instance){instance = new SafeSingleton();}}return instance;}}

4. 多状态可变

多状态可变分为两类,一类是状态是相互独立的,只要保证对每个状态的操作是原子性的,就能保证是线程安全

public class User{private int age;private double weight;public synchronized int increaseAge(){return age++;}public synchronized double increaseWeight(){return weight++;}}

但是对于多个相互依赖的状态,就需要对多个状态同时加锁

public class CachingFactorizer{private final AtomicReference<BigInteger> lastNumber = new AtomicReference<BigInteger>();private final AtomicReference<BigInteger[]> lastFactors = new AtomicReference<BigInteger[]>();public void service(BigInteger bInt){if(bInt.equals(lastNumber.get())){System.out.println("Results exist.");doWithFactors(lastFactors);}else{lastFactors = extractNumbers(bInt);lastNumber.set(bInt);}}}这个类的对象有两个状态,两个状态相互依赖,分别是上一次因式分解的数和其因子。当A线程里要进行因式分解的数等于上一次的数字,那么进入if条件语句,此时轮动线程B的时间片,其要进行因式分解的数不为上一次的数,进入else语句,因式分解,并修改上一次因式分解的数和因子为现在的数字和因子。B线程结束。A线程继续执行,对因子处理。结果不正确,因为这个因子已经变成线程B保存下来的数的因子,而不是线程A本线程需要处理的数的因子。虽然对这个对象的两个状态的操作分别是原子性的,但是结果不正确了,所以对多状态同时加锁。对service方法加内置锁就能达到目的。

public class CachingFactorizer{private final AtomicReference<BigInteger> lastNumber = new AtomicReference<BigInteger>();private final AtomicReference<BigInteger[]> lastFactors = new AtomicReference<BigInteger[]>();public synchronized void service(BigInteger bInt){if(bInt.equals(lastNumber.get())){System.out.println("Results exist.");doWithFactors(lastFactors);}else{lastFactors = extractNumbers(bInt);lastNumber.set(bInt);}}}不要再以任何人说你,因为你不是为任何人而活,你只为自己而活,

Java并发学习笔记(六)-互斥性和内存可见性

相关文章:

你感兴趣的文章:

标签云: