第二章

1,什么是对象的状态

对象的状态是指对象的实例域,或静态域中的数据。注意,对象的状态可能与依赖对象有关。例,HashMap的状态还与entry的对象相关。

2,什么是线程安全

线程安全:当多个线程同时访问一个对象(类)时(状态转变),不在需要任何的额外的同步策略,并且这个类能够变现出正确的行为,不能破坏不变形条件。

补充一点:不变形约束,当多个状态变量之间不是独立的,又相互的关系。当更新其中一个时,其他的也要变化。如果这个变化不是原子的,这可能会有线程安全的问题。

无状态对象一定线程安全。

3,什么是原子性

原子性是指在指令执行的不可分割。java的++不是原子性。非原子性是需要同步的原因。

4,竞争条件

不同的执行顺序可能会有不同的结果。正确的结果依赖于精确的时序。例:先检查后执行,是否为真,如果为执行,但是可能执行的时候,添加已经为假了。

5,复合操作

例,读取-修改-写回。若要线程安全,必须要让复合操作具有原子性,通过持有锁来实现,对同一个共享状态必须是同一个锁。

6,同时写一个原子变量会怎样?

依赖于总线仲裁。

7,java的内置锁

synchronized (lock){

}

两个功能,可见性和同步。

内置锁可以重入,注意,重入的粒度是线程,而不是调用(与posix线程不同).。????????????????????

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;
    }
}


只是同步set是不行的,因为get可能读取之后缓存,修改之后的值不可见。


4,读取费volatile的64位long和double,java内存模型把读操作和写操作分成两个32位,即long和double 费原子协定。但事实上,所有的商用虚拟机都将他们实现为原子操作,所以可以不用volatile。


5,volatile变量:保证可见性,不重排序该变量上的操作。但是访问volatile变量不会加锁么就是不会阻塞。费volatile变量上的原子操作不会刷新到内存中去,原子操作不保证可见性。volatile也不保证原子性,对volatile变量的++操作依然是非原子的。只是volatile相当于直接面向内存而非缓存。


volatile的典型用法:判断条件,是否退出循环。


 


6,对象的发布与逸出


发布:对象在他的作用域外有引用。常见的发布途径:传递参数,返回,作为其他对象的成员一起被发布(间接发布),传递内部类对象给某方法,外部对象被发布。


在构造函数中新建线程,或者调用可以改变实例状态的方法(参数对象的方法),会使this引用逸出。最好不要在构造器中让线程启动,可以以后再start。


逸出:不该发布的发布了。


 


7,线程封闭


如果一个对象是线程封闭的,不采取同步策略,也是线程安全的。其他的线程不能访问。局部变量,TreadLocal类。例,jdbc的连接池,直到一个连接返回连接池,其他的线程不能访获得该连接。


8,ThreadLocal类


该类的对象对每一个共享该对象的线程维护一个副本,也就是说,每个线程都是得到该线程上次修改的状态值。


例:一个全局的数据库连接对象。每个线程都有自己的连接。


public class G {
public ThreadLocal<Connection> getConnect() {
return new ThreadLocal<Connection> (){
public Connection get() {
return DriverManager.getConnetion (DB_URL);
            }    
        }

    }

}


当某个线程调用ThreadLocal.get()时,会用InitialValue来获取初始值,可以理解为map<thread,Connection>.,但实现并非如此。


 


9,不可变对象一定是线程安全的。final域能确保初始化过程的安全性。仍然可以更新不可变对象的状态,通过新建一个实例替换。例:String.


不可变对象不一定域为final,域为final也是可以改变的。


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);
  }
}



没有提供改变状态的方法。

 


10安全发布对象。一个例子,来源


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)同样能实现安全发布。