并发多线程基本知识

来源:互联网 发布:js数组去重的方法sp 编辑:程序博客网 时间:2024/05/21 06:27

写在前面:最近在学习Java并发编程方面的知识,在读《实战Java高并发程序设计》这本书,也算是把读书过程中重要的知识点记录下来,方便以后查阅。

首先我们知道现代计算机都运用了流水线技术,使得CPU的效率大大提高,同时也在极力避免的一件事就是不让流水线中断,因为一旦中断效率损失会非常大。但一般我们写出的程序按顺序执行很难做到流水线无中断,原因很简单,下一条指令的执行可能会依赖上一条指令的结果,就不得不等待上一条指令执行结束才能继续。但CPU很聪明,它会将指令重排序,以达到流水线最理想的效果。

由上面的引子我们开始介绍Java内存模型(JMM)关于多线程的一些关键点。

1.原子性 2.可见性 3.有序性


原子性

原子性是指一个操作不可中断的,即使在多个线程一起执行的时候,一个操作一旦开始也不会被其他线程干扰。例如有一个全局静态常量i,线程A和线程B都对它进行赋值运算,A将其赋值为1,B将其赋值为2,但无论怎样调用这两个线程,i的结果要么是1要么是2,不可能再有其他情况,A和B在各自执行时是相互独立的,互补干扰的,这就是原子性的特点,和数据库中事务的原子性类似。

可见性

Created with Raphaël 2.1.0线程1线程1主存主存线程2线程2读取变量i读取变量i

假设全局变量i=0,线程1和线程2都从主存中获取,但这两个线程中的一个对该变量进行修改时两一个变量并不能立即知道,因为每个线程都护维护一个属于该线程本身的内存块,其他线程无法读取到。假设线程1已经将i赋值为1,而线程2读取i的数值时还是0,这势必对程序造成糟糕的影响。

有序性

有序性其实在开始时已经提到,但远不止那些。在单线程中我们可以认为指令的执行顺序即为我们代码的顺序,但多线程中就不一定了,因为指令重排的存在会导致两个线程中出现无法预料的情况。这样说估计有人就有疑问了,一直说指令重排会改变顺序,难道是随便改变的吗,其实不是。指令重排是有一个基本前提的,那就是保证串行语义的正确性,但多线程之间就无法保证语义的一致性了。

class Example{    int a=0;    boolean flag=false;    public void writer(){        a=1;        flag=true;    }    public void reader(){        if(flag){            int i=a+1;        }    }}

假设线程1先执行writer()方法,线程2执行reader()方法,如果发生指令重排(一种可能情况)线程2给i赋值时并不一定知道a已经被赋值为1了,这就与我们预先设计程序时想要达到的效果不一样。

哪些指令不能重排, Happen-Before原则

  • 程序顺序原则:一个线程内保证语义的串行性
  • volatile规则:volatile变量的写先发生于读,保证volatile变量的可见性
  • 锁规则:解锁动作必然发生在加锁前
  • 传递性:A先于B,B先于C,则A先于C
  • 线程的Start()方法先于它的每一个动作
  • 线程的所有操作先于线程的终结
  • 线程的中断先于被中断线程的代码
  • 对象的构造函数执行,结束先于finalize()方法

多线程中的一些基本概念

同步(Synchronous)和异步(Asynchronous)

同步就是指一个线程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个线程将会一直等待下去,直到收到返回信息才继续执行下去;异步是指线程不需要一直等下去,而是继续执行下面的操作,不管其他线程的状态。当有消息返回时系统会通知线程进行处理,这样可以提高执行的效率。

并发(Concurrency)和并行(Parallelism)

并发偏向于多个任务交替执行,多个任务之间可能是串行执行的,系统不停的在多个线程之间切换。而并行是真正意义上同时执行。

临界区(Critical Section)

临界区表示公共资源或共享数据,但每次只有一个线程能使用它,其他线程若在此时想使用的话就必须等待。

阻塞(Blocking)和非阻塞(Non-Blocking)

如果一个线程占用了临界区的资源,其他需要这个资源的线程的线程及必须等待,从而导致线程被挂起,这种情况就是阻塞。相反就是非阻塞

死锁(Deadlock),饥饿(Starvation),活锁(Livaelock)

死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,都占有了一些资源但同时还需要其他线程的资源。
一个或多个线程因为某些原因以一直无法获得所需资源,导致一直无法执行的情况就是饥饿。
活锁是指如果两个线程间互相谦让资源,主动将资源释放给其他线程使用,那么资源就会在两个线程间一直跳动,而没有一个线程能够正常执行。

0 0
原创粉丝点击