并发模型—共享内存模型(线程与锁)示例篇

共享内存模型,顾名思义就是通过共享内存来实现并发的模型,当多个线程在并发执行中使用共享资源时如不对所共享的资源进行约定或特殊处理时就会出现读到脏数据、无效数据等问题;而为了决解共享资源所引起的这些问题,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

时间: 2024-10-31 20:46:24

并发模型—共享内存模型(线程与锁)示例篇的相关文章

并发艺术--java内存模型

前言 本章大致分为四部分. java内存模型的基础,介绍内存模型的相关基本概念: java内存模型中的顺序一致性,主要介绍重排序和顺序一致性: 同步原语,涉及synchronized,volatile,final三个同步原语的内存含义及重排序等: java内存模型的设计,涉及与内存模型和顺序一致性内存模型关系. 一.java内存模型基础  1.1 并发编码模型的两个关键问题--线程是并发执行的活动实体 线程之间如何通信 共享内存 - 通过写-读 内存中的公共状态进行隐式通信,java采用的是共享

【死磕Java并发】-----Java内存模型之分析volatile

前篇博客[死磕Java并发]-–深入分析volatile的实现原理 中已经阐述了volatile的特性了: volatile可见性:对一个volatile的读,总可以看到对这个变量最终的写: volatile原子性:volatile对单个读/写具有原子性(32位Long.Double),但是复合操作除外,例如i++; JVM底层采用"内存屏障"来实现volatile语义 下面LZ就通过happens-before原则和volatile的内存语义两个方向介绍volatile. volat

并发技术、进程、线程和锁拾遗

并发技术.进程.线程和锁拾遗 Part1. 多任务 计算机发展起初,CPU 资源十分昂贵,如果让 CPU 只能运行一个程序那么当 CPU 空闲下来(例如等待 I/O 时),CPU 资源就会被浪费,为了使 CPU 资源得到更好的利用,先驱编写了一个监控程序,如果发现某个程序暂时无需使用 CPU 时,监控程序就把另外的正在等待 CPU 资源的程序启动起来,以充分利用 CPU资源.这种方法称为 - 多道程序(Multiprogramming) 对于多道程序,最大的弊端是各程序之间不区分轻重缓急,对于用

并发编程-Java内存模型到底是什么

内存模型 在计算机CPU,内存,IO三者之间速度差异,为了提高系统性能,对这三者速度进行平衡. CPU 增加了缓存,以均衡与内存的速度差异: 操作系统增加了进程.线程,以分时复用 CPU,进而均衡 CPU 与 I/O 设备的速度差异: 编译程序优化指令执行次序,使得缓存能够得到更加合理地利用. 以上三种系统优化,对于硬件的效率有了显著的提升,但是他们同时也带来了可见性,原子性以及顺序性等问题.基于Cpu高速缓存的存储交互很好得解决了CPU和内存得速度矛盾,但是也提高了计算机系统得复杂度,引入了新

并发编程-Java内存模型:解决可见性与有序性问题

背景 我们知道导致cpu缓存导致了可见性问题,编译器优化带来了有序性问题.那么如果我们禁用了cpu缓存与编译器优化,就能够解决问题,但是性能就无法提升了.所以一个合理的方案,就是按照一定规范来禁用缓存和编译器优化,即在某些情况下禁用缓存与编译器优化.Java内存模型就是这样的一个规范,用来解决可见性与有序性问题 概念 java内存模型本质上就是规范了JVM如何按照规则禁用缓存和编译器优化,既面向应用开发人员,也面向jvm的实现.这些规范包括了volatile.synchronized和final

linux进程间通信之System V共享内存详解及代码示例

共享内存是最快最为高效的进程间通信方式,当共享内存映射到共享它的某个进程的地址空间后,进程间的数据传递就不再牵扯到内核,进程可以直接读取内核,不需要通过内核系统调用进行数据拷贝.一般使用情况,从共享内存中写入或读取数据的进程间需要做同步,例如通过信号量,互斥锁去同步. 共享内存有System V 共享内存和Posix共享内存,本文介绍System V 共享内存.System V共享内存头文件及相关函数原型:#include <sys/shm.h> int shmget(key_t key, s

高并发编程原理与实战.线程安全.锁原理.同步容器.实战之JAVA架构

1.什么叫容器? ----->数组,对象,集合等等都是容器. 2.什么叫同步容器? ----->Vector,ArrayList,HashMap等等. 3.在多线程环境下,为什么不用同步容器呢? ---->1.线程不安全问题.2.线程安全的情况下,但是性能非常差问题. Vector(线程安全,基本不用)---->ArrayList(线程不安全)---->使用Collections.synchronizedList()将ArrayList转成线程安全(性能非常差,不常用)---

Java并发编程的艺术(五)Java内存模型

1.并发编程模型的两个关键问题:线程之间的通信和同步. 2.并发线程模型有两种: (1)共享内存:线程之间有公共状态,通过读-写内存中的公共状态进行隐式通信,显示同步. (2)消息传递:线程之间没有公共状态,必须通过发送消息进行显示通信,隐式同步. 3.Java的并发采用共享内存模型,线程间的通信总是隐式进行,整个通信过程对程序员完全透明. 4.Java中堆内存存储的共享变量(实例.静态和数组元素)在线程之间共享,局部变量.方法定义参数和异常处理器参数不会共享(也就不存在内存可见性问题). 5.

(转)【Java线程】Java内存模型总结

Java的并发采用的是共享内存模型(而非消息传递模型),线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信.多个线程之间是不能直接传递数据交互的,它们之间的交互只能通过共享变量来实现 同步是显式进行的.程序员必须显式指定某个方法或某段代码需要在线程之间互斥执行. 1.多线程通信 1.1 内存模型 Java线程之间的通信由Java内存模型(JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见. 从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程