共享内存模型,顾名思义就是通过共享内存来实现并发的模型,当多个线程在并发执行中使用共享资源时如不对所共享的资源进行约定或特殊处理时就会出现读到脏数据、无效数据等问题;而为了决解共享资源所引起的这些问题,Java中引入了同步、锁、原子类型等这些用于处理共享资源的操作;
在本篇文章中,将通过几个Demo来介绍Java的synchronized、lock、atomic相关类等,Java的共享内存并发模型也就体现在同步(synchronized)、锁(lock)等这些实现上;
同步:
Demo中开启两个线程调用一个计数器,这时计数器成了两个线程的共享资源,两个线程都在竞争共享资源发生了竟态条件,当不对竟态条件进行处理时得到的数据就可能是有异常的不正确的;
1 /** 2 * Created by linx on 2015-05-12. 3 */ 4 public class Counter { 6 private int count = 0; 8 public void increment() { 9 ++count; 10 } 12 public int getCount() { 13 return count; 14 } 15 } 16 /** 17 * Created by linx on 2015-05-12. 18 */ 19 public class CountThread extends Thread { 20 21 private Counter counter; 22 public CountThread(Counter counter) { 23 this.counter = counter; 24 } 25 @Override 26 public void run() { 27 for (int i = 0; i < 5000; i++) { 28 counter.increment(); 29 } 30 } 31 } 32 /** 33 * Created by linx on 2015-05-12. 34 */ 35 public class CountMain { 36 37 public static void main(String[] args) throws InterruptedException { 38 39 Counter counter = new Counter(); 40 AtomicCounter atomicCounter=new AtomicCounter(); 41 CountThread t1 = new CountThread(counter); 42 CountThread t2 = new CountThread(counter); 43 t1.start(); 44 t2.start(); 45 t1.join(); 46 t2.join(); 47 System.out.println(counter.getCount()); 48 } 49 }
我在执行这代码的时候几乎每次得到的结果都是不一样的,结果如下:
因为这里有竟态条件所以结果是不可预测的;
解决竟态条件的方法是对锁竞争的资源进行加锁同步,在java中可以用synchronized或lock等锁;
现在我们再修改计数器的代码:
public synchronized void increment() { ++count; }
这里我们只是在increment方法声明处加了synchronized关键字,这时候我们在执行程序,现在每次我们得到结果都会是10000,
因为我们解决了竟态条件,同一时间就会有一个线程会进入到increment方法执行,所以这时候得到的就是正确的结果;
锁
在这里我们只是把上面Demo中的synchronized换成Lock对象,得到的结果还是相同的;
/** * Created by linx on 2015-05-12. */ public class Counter { private int count = 0; Lock lock=new ReentrantLock(); public void increment() { lock.lock(); try { ++count; }finally { lock.unlock(); } } public int getCount() { return count; } }
这里我们显示的使用了显试的ReentrantLock锁对象给increment方法中的代码块进行了加锁,其他synchronized也是对方法进行了加锁,不过它使用的是对象的内置锁;
原子类型
我们上面的Demo只所以没有同步或加锁时会出现问题是因为++count不是原子的,它其实是read-modify-write三个操作,只要能保证increment为原子方法那么这里也就不是出现问题了,现在我们吧count改为原子类型;
/** * Created by linx on 2015-05-12. */ public class AtomicCounter { private AtomicInteger count=new AtomicInteger(); public void increment() { count.incrementAndGet(); } public AtomicInteger getCount() { return count; } }
这个计数器类我们不进行任何同步或加锁都不会出现问题,因为increment方法是原子的。
模型优缺点
优点:内存共享模型或称线程与锁模型使用面很广,而且现在几乎每个操作系统中也存在这种模型,所以也算非常见的一种模型。
缺点:线程与锁模型存在的一些问题有,没有直接支持并发、无法或难于实现分布式共享内存的系统,线程与锁模型有非常不好的地方就是难于测试,在多线程编程中很多时候不经意间就出现问题了这时都还不知道,而且当突然出现了Bug这时往往我们也难于重现这个Bug,共享内存模型又是不可建立数学模型的,其中有很大的不确定性,而不确定性就说明可能掩藏着问题,人的思维也只是单线程的;
还有由于创建线程也是非常消耗资源的,而多线程间的竟态条件、锁等竞争如果处理不好也是会非常影响性能的;
文章首发地址:Solinx
http://www.solinx.co/archives/190