第二章
1,什么是对象的状态
对象的状态是指对象的实例域,或静态域中的数据。注意,对象的状态可能与依赖对象有关。例,HashMap的状态还与entry的对象相关。
2,什么是线程安全
线程安全:当多个线程同时访问一个对象(类)时(状态转变),不在需要任何的额外的同步策略,并且这个类能够变现出正确的行为,不能破坏不变形条件。
补充一点:不变形约束,当多个状态变量之间不是独立的,又相互的关系。当更新其中一个时,其他的也要变化。如果这个变化不是原子的,这可能会有线程安全的问题。
无状态对象一定线程安全。
3,什么是原子性
原子性是指在指令执行的不可分割。java的++不是原子性。非原子性是需要同步的原因。
4,竞争条件
不同的执行顺序可能会有不同的结果。正确的结果依赖于精确的时序。例:先检查后执行,是否为真,如果为执行,但是可能执行的时候,添加已经为假了。
5,复合操作
例,读取-修改-写回。若要线程安全,必须要让复合操作具有原子性,通过持有锁来实现,对同一个共享状态必须是同一个锁。
6,同时写一个原子变量会怎样?
依赖于总线仲裁。
7,java的内置锁
synchronized (lock){
}
两个功能,可见性和同步。
内置锁可以重入,注意,重入的粒度是线程,而不是调用(与posix线程不同).。????????????????????
可见性可以看一下java的内存模型。推荐http://tutorials.jenkov.com/java-concurrency/java-memory-model.html
8,每个对象都有一个内置锁,他本身。每个共享状态都应该只有同一个锁保护。不变性条件中的每一个锁都需要同一个锁保护。
如果只是将类的所有方法都添加sysnchronized,并不能保证同步。例:
if( !a.contain(o) )
a.insert(o)
虽然contain和insert都synchronized,但这是一个复合操作。
一些技巧:
1,可以通过线程安全的对象管理不安全的对象,达到线程安全的目的。
第三章
1,synchronized的作用: 可见性,原子性,临界区。
可见性:为了性能,每个线程在访问共享状态的时候,会对共享状态进行缓存,当修改变量的的时候也是在缓存中修改,一个线程的修改可能对其他线程不可见,从而使共享状态不一致。synchronized和volatile可以保证可见性。每次读会从内存中读取,修改会写回内存。
2,如下没有同步的代码有哪些问题?
package gkl;
public class T{
static boolean a;
static int b;
private static class A extends Thread{
public void run() {
while(!a)
Thread.yield();
System.out.println(b);
}
}
public static void main(String[] args) {
A thread=new A();
thread.start();
a=true;
b=10;
}
}
1),死循环,因为线程可能看不见a的修改。
2),输出为0,因为重排序,可能看见了a的修改,但是没有看见b的修改。
3,下面的代码有什么问题?
class Unsafe{
int value;
int get(){
return value;
}
synchronized void set(int a){
value=a;
}
}
public class G {
public ThreadLocal<Connection> getConnect() {
return new ThreadLocal<Connection> (){
public Connection get() {
return DriverManager.getConnetion (DB_URL);
}
}
}
}
public final class ThreeStooges {
private final Set<String> stooges = new HashSet<String>();
public ThreeStooges() {
stooges.add("Moe");
stooges.add("Larry");
stooges.add("Curly");
}
public boolean isStooge(String name) {
return stooges.contains(name);
}
}
public class Holder {
private int n;
public Holder() {
this.n = n;
outter = this; //这里this逸出(outter是一个外部使用的对象)
}
public void assertSanity() {
if(n != n)
throw new AssertionError("This statement is false.");
}
}
public class SuperHolder
{
public Holder holder;
public static void main(String... args)
{
final SuperHolder sh = new SuperHolder();
new Thread(new Runnable(){
public void run()
{
sh.holder = new Holder();
}
}).start();
new Thread(new Runnable(){
public void run()
{
if(sh.holder!=null)
{
sh.holder.assertSanity();
}
}
}).start();
}
}
如果是“不可变对象”,只要正确构造了,无论如何JVM都能保证它被“安全发布”。
如果不是“不可变对象”,即使正确构造,但是JVM并不保证它能被“安全发布”。
n != n也不是原子的!原因就是构造函数不是原子的,指令可能重排序。”
其编译后的java指令为
aload
getfield //第一次读变量n
aload
getfield //第二次读变量n
if_icmpeq
如果不同步的话,两次读变量n之间是可能会切换到其它线程执行的。
继续使用我给出的“不安全发布”的例子来说
第一个线程用来new实例,当第二个线程中 sh.holder!=null 时(也就是构造函数已经发布了),但因为重排序,n有可能尚未赋值,也就是int变量的默认值0。此时第一次读变量n记为n',实际值为0;此时再次切换到线程1完成了n赋值为42,此时第二次读变量n记为n'',实际值为42。
你所看到的n!=n实际是n'!=n'',最后是0!=42,结果为真
(表示感谢)10,安全发布对象的常见模式。这儿有必要说一下安全发布的含义。安全发布不意味着线程安全,指的是对象发布的那一刻,对所有的线程,他的引用和状态是可见的,不保证以后的状态对其他线程依然可见,而需要使用同步。上面的例子之所以是不安全发布,就是因为对象的状态对其他线程不可见,不知道有没有赋值。
- 在静态初始化函数中初始化一个对象引用
- 将对象的应用保存到volatile类型的域或者AtomicReferance对象中
- 将对象的引用保存到某个正确构造对象的final类型域中 (保证初始化的安全性)
- 将对象的引用保存到一个由锁保护的域中。(Hashtable、synchronizedMap或者ConcurrentMap,Vector、CopyOnWriteArrayList、CopyOnWriteArraySet、synchronizedList或synchronizedSet,BlockingQueue或者ConcurrentLinkedQueue)
类库中的其他数据传递机制(例如Future和Exchanger)同样能实现安全发布。