高效并发(二)

并发基本知识总结(二)

前言

       在学习完虚拟机之后,我们了解了java的内存模型、实现线程安全的方法和jvm的一些锁优化机制,我们现在就把方向在往线程的基本操作,到concurrent包里面的线程安全类、原子类和一些lock的实现类。了解整个java并发基础。

传统方式

产生线程的方法

  1. 继承thread,重写run方法
  2. thread+runnable
  3. callable+future(新型)

控制线程

  1. join
  2. sleep
  3. yield
  4. 修改优先级

传统线程里面的通信

  1. notify
  2. notifyAll
  3. wait

并发必要知识

       由第一节我们知道了jvm的内存模型的特点:原子性、可见性、有序性。其中多个过程之间内存可见性的顺序可能不一致,比如下面的程序例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Test1 {
private int a=1, b=2;
public void foo(){ // 线程1
a=3;
b=4;
}
public int getA(){ // 线程2
return a;
}
public int getB(){ // 线程2
return b;
}
}
//可能出现的结果
A:a=1, b=2 // 都未改变
B:a=3, b=4 // 都改变了
C:a=3, b=2 // a改变了,b未改变
D:a=1, b=4 // b改变了,a未改变

造成这个的原因有下面两个:



1. Java编译器的重排序(Reording)操作有可能导致执行顺序和代码顺序不一致


2. 从线程工作内存写回主存时顺序无法保证。



       正因为上面的那些问题,JMM中一个重要问题就是:如何让多线程之间,对象的状态对于各线程的“可视性”是顺序一致的。它的解决方式就是 Happens-before 规则:
JMM为所有程序内部动作定义了一个偏序关系,叫做happens-before。要想保证执行动作B的线程看到动作A的结果(无论A和B是否发生在同一个线程中),A和B之间就必须满足happens-before关系。



       我们重点关注的是②,③,这两条也是我们通常编程中常用的。也就是加锁(包括新的lock类和synchronized)和volatile(语义增强了),当然也有不变类(final)和atomic原子类。

监视器

       早期Java中的锁只有最基本的synchronized,它是一种互斥的实现方式。在Java5之后,增加了一些其它锁,比如ReentrantLock,它基本作用和synchronized相似,但提供了更多的操作方式,比如在获取锁时不必像synchronized那样只是傻等,可以设置定时,轮询,或者中断,这些方法使得它在获取多个锁的情况可以避免死锁操作。

       而我们需要了解的是ReentrantLock的性能相对synchronized来说有很大的提高。(不过据说Java6后对synchronized进行了优化,两者已经接近了。)在ConcurrentHashMap中,每个hash区间使用的锁正是ReentrantLock。

增强的volatile语义

       Volatile可以看做一种轻量级的锁,但又和锁有些不同。

  1. 它对于多线程,不是一种互斥(mutex)关系。
  2. 用volatile修饰的变量,不能保证该变量状态的改变对于其他线程来说是一种“原子化操作”。

       在Java5之前,JMM对Volatile的定义是:保证读写volatile都直接发生在main memory中,线程的working memory不进行缓存。它只承诺了读和写过程的可见性,并没有对Reording做限制,所以旧的Volatile并不太可靠。在Java5之后,JMM对volatile的语义进行了增强。就是我们看到的③ volatile变量法则。对volatile域的写入操作先行于每一个后续的读写操作。

       所以,在使用Volatile时,需要注意:首先,需不需要互斥;其次,对象状态的改变是不是原子化的。它可以简化实现或者同步策略,确保引用对象的可见性,比如标志生命周期的事件:开始或者关闭。

       脆弱的使用条件:

  1. 写入变量不依赖变量的当前值,或者能够保证只有单一的线程修改变量的值。
  2. 变量不需要和其他变量共同参与不变约束。
  3. 访问变量时不需要其他原因加锁。

    不变模式(immutable)

           多线程安全里最简单的一种保障方式。因为你拿他没有办法,想改变它也没有机会。不变模式主要通过final关键字来限定的。在JMM中final关键字还有特殊的语义。Final域使得确保初始化安全性(initialization safety)成为可能,初始化安全性让不可变形对象不需要同步就能自由地被访问和共享。

原子类

       如果操作都是原子操作,那就实现线程安全了。

基本知识

       四种进程或线程同步互斥的控制方法:

  1. 临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。

  2. 互斥量:为协调共同对一个共享资源的单独访问而设计的。

  3. 信号量:为控制一个具有有限数量用户资源而设计。

  4. 事件:用来通知线程有一些事件已发生,从而启动后继任务的开始。

说明

       文中出现的图片,文字描述有些来自互联网,但是出处无法考究,如果侵犯您的相关权益,请联系我,核实后我会马上加上转载说明。谢谢!!!q q q

坚持原创技术分享,您的支持将鼓励我继续创作!