java并发编程实战 notes

来源:互联网 发布:英语在线交流软件 编辑:程序博客网 时间:2024/06/06 02:10

1、简介

早期的计算机不包含操作系统,从头到尾只能执行一个程序。
之所以在计算机中加入操作系统来实现多人程序的同时运行,原因:
资源利用率; 公平性;便利性:编写多个程序,每个程序执行一个任务,比编写一个程序来计算所有任务更容易。
竞态条件 race codition
活跃性问题:安全性是“永远不发生糟糕的事情”,活跃性是“某件正确的事情最终会发生”。 活跃性问题有无限循环、永久等待等。
性能问题:正确的事情尽快发生。

2、线程安全性

如果多个线程访问同一个可变的状态变量没有使用合适的同步,那么程序就会出现错误,有三种方式可以修复这个问题:
1)不在线程之间共享该状态变量
2)将状态变量修改为不可变的变量
3)在访问状态变量时使用同步
无状态对象都是线程安全的。
竞态条件的本质:基于一种可能失效的观察结果来做出判断或者执行某个计算。称为“先检查后执行”
要保证状态一致性,就需要在单个原子操作中更新所有相关的状态变量。
重入:某个线程试图获取一个已经由它自己持有的锁,那么这个请求就会成功。“重入”意味着获取锁的操作的粒度是”线程”而不是“调用”。
重入的一种实现方式是为每个锁关联一个计数器和一个所有者线程。
重入避免了以下两种情况发生死锁:
1. 有synchronized修饰的方法递归调用。
2. 子类在重写的方法中调用父类相同的方法:
class FatherC {
public synchronized void doSomething() {
}
}
class childrenC extends FatherC {
@Override
public synchronized void doSomething() {
System.out.println("call father override method");
super.doSomething();
}
}

通常,在简单性与性能之间存在着相互制约因素。当实现某个同步策略时,一定不要盲目地为了性能而牺牲简单性(这可能破坏安全性)
当执行时间较长的计算或者可能无法快速完成的操作时(例如,网络I/O或控制台I/O),一定不要持有锁。

3、对象的共享

synchronized不仅实现原子性,也实现了内存可见性。
为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制。
如果没有同步机制,线程A就可能看不到线程B对于状态的修改,这种现象称为“重排序”。无法确定线程中的操作将按照程序中指定的顺序来执行。
解决方法:只要有数据在多个线程之间共享,就使用正确的同步。
在访问某个共享且可变的变量时要求所有线程在同一个锁上同步,就是为了确保某个线程写入该变量的值对于其他线程来说都是可见的。
加锁的含义不仅局限于互斥行为,还包括内存可见性。为了确保所有线程都能看到共享变量的最新值,所有执行读操作或者写操作的线程必须在同一个锁上同步
volatile: 把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或都对其他处理器不可见的地方,因此在读取volatile变量时总是会返回最新写入的值
访问volatile变量不会执行加锁操作,因此不会使执行线程阻塞。volatile保证了可见性,不保证原子性。
仅当volatile变量能简化代码的实现以及对同步策略的验证时,才应该使用它。
volatile的一种典型用法:检查状态标记以判断是否退出循环。
volatile通常用做某个操作完成、发生中断或者状态的标志。
当且仅当满足以下所有条件时,才应该使用volatile:
当变量的写入操作不依赖变量的当前值,或者能确保只有单个线程更新变量
该变量不会与其他状态变量一起纳入不变性条件中
在访问变量时不需要加锁
发布与逸出:发布指使对象能够在当前作用域之外的代码中使用。 逸出指某个不应该发布的对象被发布时,称为逸出。
如果在类的非私有方法中返回一个引用,那么同样会发布返回的对象。
当一个对象返回给某个外部方法时,就相当于发布了这个对象。
封装能够使得对程序的正确性进行分析变得可能,并使得无意中破坏设计约束条件变得更难。
最后一个发布对象或其内部状态的机制是发布一个内部的类实例,如下所示代码,因为在这个内部类的实例中包含了对ThistEscape实例的隐含引用。
class ThisEscape{
public ThisEscape(EventSource e) {
source.registerListener (
new EventListener() {
public void onEvent(Event e) {
doSomething(e);
}
});
}
}

不要在构造过程中使this引用逸出。
线程封闭: 仅在单线程内共享数据。线程封闭是在程序设计中的一种考虑因素,必须在程序中实现。java语言及其核心库提供了一些机制来帮助维持线程封闭性,如局部变量和threadLocal.
Ad-hoc线程封闭:维护线程封闭性的职责完全由程序实现来承担。 很脆弱,容易出错
栈封闭:只能通过局部变量才能访问对象。
ThreadLocal类:为每个使用该变量的线程都存有一份独立的副本。 只在初次调用时执行init,以后都从临时对象里取。
不变性: 不可变对象一定是线程安全的。
不可变性不等于将对象中所有的域都声明为final类型,即使对象中所有的域都声明为final,这个对象也仍然是可变的,因为在final类型的域中可能保存对可变对象的引用。
当满足以下条件时,对象才是不可变的:
1)对象创建以后其状态变不能修改
2)对象的所有域都是final的
3)对象是正确创建的(创建期间,this引用没有溢出)
不可变对象的内部仍然可以使用可变对象来管理它们的状态。
正如“除非需要更高的可见性,否则应将所有的域都声明为private”是一个良好的编程习惯,“除非需要某个域是可变的,否则应将其声明为final”也是一个良好的编程习惯。
安全发布对象:要安全发布一个对象,对象的引用以及对象的状态必须同时地其他线程可见。一个正确的构造的对象可以通过以下方式安全的发布:
在静态初始化函数中初始化一个对象引用。
将对象的引用保存到volatile类型的域或者AtomicReference对象中。
将对象的引用保存到某个正确构造对象的final域中
将对象的引用保存到一个由锁保护的域中。
如果一个对象从技术上讲是可变的,但其状态在发布后不会改变,那么这种对象称为“事实不可变对象”。
对象的发布需求取决于它的可变性:
不可变对象可以通过任意机制来发布
事实不可变对象必须通过安全方式来发布
可变对象必须通过安全方式来发布,并且必须是线程安全的或者是凯瑞某个锁保护起来。



未完待续。。。
1 0
原创粉丝点击