并发基本知识总结(二)
前言
在学习完虚拟机之后,我们了解了java的内存模型、实现线程安全的方法和jvm的一些锁优化机制,我们现在就把方向在往线程的基本操作,到concurrent包里面的线程安全类、原子类和一些lock的实现类。了解整个java并发基础。
传统方式
产生线程的方法
- 继承thread,重写run方法
- thread+runnable
- callable+future(新型)
控制线程
- join
- sleep
- yield
- 修改优先级
传统线程里面的通信
- notify
- notifyAll
- wait
并发必要知识
由第一节我们知道了jvm的内存模型的特点:原子性、可见性、有序性。其中多个过程之间内存可见性的顺序可能不一致,比如下面的程序例子。
|
|
造成这个的原因有下面两个:
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可以看做一种轻量级的锁,但又和锁有些不同。
- 它对于多线程,不是一种互斥(mutex)关系。
- 用volatile修饰的变量,不能保证该变量状态的改变对于其他线程来说是一种“原子化操作”。
在Java5之前,JMM对Volatile的定义是:保证读写volatile都直接发生在main memory中,线程的working memory不进行缓存。它只承诺了读和写过程的可见性,并没有对Reording做限制,所以旧的Volatile并不太可靠。在Java5之后,JMM对volatile的语义进行了增强。就是我们看到的③ volatile变量法则。对volatile域的写入操作先行于每一个后续的读写操作。
所以,在使用Volatile时,需要注意:首先,需不需要互斥;其次,对象状态的改变是不是原子化的。它可以简化实现或者同步策略,确保引用对象的可见性,比如标志生命周期的事件:开始或者关闭。
脆弱的使用条件:
- 写入变量不依赖变量的当前值,或者能够保证只有单一的线程修改变量的值。
- 变量不需要和其他变量共同参与不变约束。
- 访问变量时不需要其他原因加锁。
不变模式(immutable)
多线程安全里最简单的一种保障方式。因为你拿他没有办法,想改变它也没有机会。不变模式主要通过final关键字来限定的。在JMM中final关键字还有特殊的语义。Final域使得确保初始化安全性(initialization safety)成为可能,初始化安全性让不可变形对象不需要同步就能自由地被访问和共享。
原子类
如果操作都是原子操作,那就实现线程安全了。
基本知识
四种进程或线程同步互斥的控制方法:
临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
互斥量:为协调共同对一个共享资源的单独访问而设计的。
信号量:为控制一个具有有限数量用户资源而设计。
事件:用来通知线程有一些事件已发生,从而启动后继任务的开始。
说明
文中出现的图片,文字描述有些来自互联网,但是出处无法考究,如果侵犯您的相关权益,请联系我,核实后我会马上加上转载说明。谢谢!!!q q q