首页 > 学院 > 开发设计 > 正文

Java并发编程

2019-11-14 23:00:32
字体:
来源:转载
供稿:网友
java并发编程声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.VEVb.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将追究法律责任!原文链接:http://www.VEVb.com/jiangzhengjun/p/4289498.html

共享对象... 4

可见性... 4

过期数据... 5

非原子性的64位操作... 5

锁和可见性... 5

volatile变量... 6

发布与逸出... 7

安全构建的实践... 8

不可变性... 9

final域... 10

示例:使用volatile发布不可变的对象... 10

安全发布... 12

不可变对象的初始化是安全的... 12

可变对象的安全发布... 13

高效不可变对象... 13

对象(不可变/可变/高效)的发布约束... 14

安全共享对象... 14

向已有的线程安全类添加功能(另附第4章)... 14

重排序(reordering)... 16

happens-before法则... 16

发布... 18

不安全的发布... 18

安全发布... 18

安全初始化技巧... 19

安全初始化(安全构造)... 19

构建块(Building Blocks)... 20

同步容器... 20

同步容器的问题... 20

迭代器和ConcurrentModificationException. 21

隐藏迭代器... 22

并发容器... 22

ConcurrentHashMap. 25

Map附加的原子操作... 25

CopyOnWriteArrayList25

阻塞队列和生产者—消费者模式... 26

示例:LinkedBlockingQueue. 29

示例:PRiorityBlockingQueue. 30

示例:DelayQueue. 31

阻塞和可中断的方法... 33

Synchronizer(同步器)... 33

CountDownLatch(倒计时闭锁)... 35

CyclicBarrier(障栅)... 37

FutureTask. 39

Semaphore(信号量)... 40

Exchanger(交换器)... 41

SynchronousQueue(同步队列)... 44

为计算结建立高效可伸缩的调整缓存... 46

使用Memoizer为因式分解的servlet缓存结果:... 49

任务执行(Task Execution)... 50

在线程中执行任务... 50

顺序地执行任务... 50

显式地为任务创建线程... 50

无限制地创建线程的缺点... 51

Executor框架(任务与执行分开)... 51

示例:使用Executor实现Web Server52

执行策略... 53

线程池... 53

Executor的生命周期... 54

定时周期性任务... 55

找出可开发的并行性... 56

示例:顺序执行的页面渲染器... 56

可携带结果的任务:Callable和Future. 57

示例:使用Future实现页面渲染器... 58

CompletionService:Executor 与 BlockingQueue的结合体... 59

示例:使用CompletionService的页面渲染器... 60

为任务设置时限... 61

方法小结... 62

Future的get、cancel、isCancelled、isDone方法... 62

ExecutorService的submit、invokeAll、invokeAny方法... 62

取消和关闭... 63

取消任务... 63

中断... 64

中断策略... 65

响应中断... 65

示例:限时运行... 66

通过Future取消任务... 69

处理不可中断阻塞方法... 70

用newTaskFor封闭非标准取消技术... 71

停止基于线程的服务... 72

示例:日志服务... 72

关闭ExecutorService. 75

终结标示对象... 77

示例:一次性服务的关闭... 79

shutdownNow的局限性... 79

处理导致线程终止的异常... 82

处理线程未捕获的异常... 83

关闭JVM.. 85

关闭钩子... 85

守护线程... 86

Finalizer86

线程池应用... 87

任务与执行策略的隐式耦合... 87

定制线程池的大小... 88

配置ThreadPoolExecutor89

线程的创建与销毁... 89

配置任务队列... 90

饱和策略... 91

线程工厂... 92

ThreadPoolExecutor构造后再配置... 96

扩展ThreadPoolExecutor96

示例:通过扩展线程提供日志和统计功能... 97

并行递归... 98

性能和可伸缩性... 99

测试并发程序... 100

正确性测试... 100

基本的单元测试... 101

测试阻塞操作... 101

线程安全性测试... 102

测试资源管理... 104

使用回调... 104

让线程产生更多的交叉操作... 105

性能测试... 105

扩展PutTakeTest,加入时间特性... 105

比较多种算法... 108

显式锁... 108

Lock和ReentrantLock. 108

立即锁、超时锁、中断锁、公平锁... 109

在synchronized和ReentrantLock的选择... 111

读-写锁... 112

构建自己的同步工具... 114

自己构造循环队列... 114

将验证失败信息传给调用者... 115

利用“轮询加休眠”实现拙劣的阻塞... 115

让条件队列来解决这一切... 116

阀门(可并可关)... 117

显示的Condition对象... 118

剖析Synchronizer119

AbstractQueuedSynchronizer121

不可重入互斥锁... 122

一个简单的闭锁... 123

原子变量与非阻塞同步机制... 124

锁的劣势... 124

硬件对并发的支持... 124

比较并交换... 125

应用模拟的CAS实现非阻塞计数器... 126

JVM对CAS的支持... 127

原子变量... 127

原子变量是“更佳的volatile”. 127

非阻塞算法... 128

非阻塞栈... 129

非阻塞链表... 130

Atomic Field Updaters132

共享对象

synchronized不仅仅只有原子性,还具有内存可见性。我们不仅希望能够避免一个线程修改其他线程正在使用的对象的状态,而且希望确保当一个线程修改了对象的状态后,其他线程能够真正看到改变。你可以使用显示的同步或者利用内置于类库中的同步机制,来保证对象的安全性。

可见性

在多线程环境下,下面程序中当ready为true时,number不一定为42,这是因为它没有使用恰当的同步机制,没有保证主线程写入ready和number的值对读线程是可见的。

public class NoVisibility {
 private static boolean ready;
 private static int number;
 private static class ReaderThread extends Thread {
 public void run() {
 while (!ready)
 Thread.yield();

//这里可能输出0,也可能永远都不会输出

 System.out.println(number);
 }
 }
 public static void main(String[] args) {
 new ReaderThread().start();
 number = 42;
 ready = true;
 }
}

这里可能会产生两个问题,一是程序可能一直保持循环,因为对于读线程来说,ready的值可能永远不可见。二是输入的number为0,这是因为重排序引起的,在写线程将ready与number从工作内存中写回到主内存中时,在没有同步的机制下,先写ready还是先写number这是不确定的,也就是说将它们写回到主内存时的顺序可能与程序逻辑顺序恰好相反,这是因为在单个线程下,只要重排序不会对结果产生影响,这是允许的。

在没有使用同步的情况下,编译器、运行器、运行时安排顺序可能完全出乎意料,在没有进行适当同步的多线程程序中,尝试推断那些“必然”发生在内存中的动作时,你总是会判断错误。

过期数据

上面NoVisibility程序在多线程环境下还可能读取到过期数据,比如当ready为true时,写线程已将number域的值置为了42,但在它还未来得及将这个新值从工作内存中写回到主内存前,读线程就已将ready从主内存中读取出来了,这时的值还是为初始的默认值0,这个值显然是一个已过期了的值,因为number现在真真的值应该为42,而不是0。

在没有同步的情况下读取数据类似于数据库中使用READ_UNCOMMITTED(未提交读)隔离级别,这时你更愿意用准确性来交换性能。

在NoVisibility中,过期数据可能导致它打印错误数值,或者程序无法终止。过期数据可能会使对象引用中的数据更加复杂,比如链指针在链表中的实现。过期数据还可能引发严重且混乱的错误,比如意外的异常,脏的数据结构,错误的计算和无限的循环。

下面的程序更对过期数据尤为敏感:如果一个线程调用了set,但还未来得及将这个新值写回到主内存中时,而另一个线程此时正在调用get,它就可能看不到更新的数据了:

@NotThreadSafe
public class MutableInteger {
 private int value;
 public int get() { return value; }
 public void set(int value) { this.value = value; }
}

我们可以将set与get同步,使之成为线程安全的。注,仅仅同步某个方法是没有用的。

非原子性的64位操作

当一个线程在没有同步的情况下读取变量,它可能会得到一个过期值。但是至少它可以看到某个线程在那里设定的一个完整而真实数值,而不是一个凭空而来的值。这样的安全保证被称为是最低限的安全性。

最低限的安全性应用于所有变量,除了一个例外:没有声明为volatile的64位数值变量double和long。Java内存模型规定获取(read动作)和存储(write动作)操作都是原子性的,但是对于非volatile的long和double变量,JVM允许将64位的读回写划分为两个32的操作。如果读和写发生在不同的线程,这种情况读取一个非volatile类型long就可能会现得到一个线程写的值的高32位和另一个线程写的值的低32位,最终这个long变量的值由这两个线程高低位组合而成的值。因此,即使你并不关心过期数据,但仅仅在多线程程序中使用共享的、可变的long和double变量也可能是不安全的,除非将它们声明为volatile类型,或者锁保护起来。

锁和可见性

内置锁可以用来确保一个线程以某种可预见的方法看到另一个线程的影响,像下图一样。当B执行到与A相同的锁监视的同步块时,A在同步块之中所做的每件事,对B都是可见的,如果没有同步,就没有这样的保证。

锁不仅仅是关于同步与互斥的,也是关于内存可见的。为了保证所有线程都能够看到共享的、可变变的最新值,读取和写入线程必须使用公共锁进行同步。

volatile变量

volatile是一种弱同步的形式,它确保对一个变量的更新后对其他线程是可见的。当一个域声明为volatile类型后,编译器与运行时会监视这个变量:它是共享的,而且对它的操作不会与其他的内存操作一起被重排序。volatile变量不会缓存在寄存器或者缓存其他处理器隐藏的地方,所以,读一个volatile类型的变量时,总会返回由某一线程所写入的最新值。

读取volatile变量的操作不会加锁,也就不会引起执行线程的阻塞,这使得volatile变量相对于sychronized而言,只是轻量级的同步机制

volatile变量对可见性的影响所产生的价值远远高于变量本身。线程A向volatile变量写入值,随后线程B读取该变量,所有A执行写操作前可见的变量的值,在B读取了这个vola

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表