JAVA并发编程之对象的共享

可见性:当读操作和写操作在不同的线程中执行时,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制。

 

@ThreadSafe

public class SysnchronizedInter{

  @GuardedBy("this") private int value;

 

  public synchronized int get(){ return value;}

  public synchronized void set(int value){this.value=value;}

}

以上线程安全类。

2、加锁与可见性:内置锁可以达到一个线程以一种可预测的方式来查看另一个线程的执行结果。但是可见的某个共享且可变的变量时要求所有线程在同一个锁上同步。如果一个线程在未持有正确锁的情况下读取这个可变变量,那么读到的这个值就可能是个失效值。

    结论:加锁的含义不仅仅局限于互斥行为,还包括内存的可见性。共享变量的最新值要让所有线程都正确获取到,必须保证这些线程都在同一个锁上同步。

3、Volatile变量:当把变量声明为volatile时,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其它内存操作一起重排序。

volatile变量不会被缓存到寄存器或者对其它处理器不可见的地方,因此在读取volatile变量时,总会返回最新写入的值。

重点理解:从内存可见性的角度来看,写入volatile变量相当于退出同步代码块,读取volatile变量就相当于进入了同步代码块。

4、线程封闭:不共享可变数据即为线程封闭。在volatile变量上存在一种特殊的线程封闭,只要ni能确保只有单个线程  对共享的volatile变量执行写入操作,那么就可以安全地在这些共享变量上面执行复合操作,相当于将修改操作封闭在单个线程中以防止发生竞态条件,而且volatile变量的可见性保证还确保了其他线程能看到最新的值。

5、栈封闭:是编程中遇到的最多的线程封闭,简单说栈封闭即局部变量使用,就是说多线程去访问一个方法时,这个方法中的局部变量都会拷贝到线程自己的线程栈中,所有这些局部变量是不会被线程共享的,所以也就不会出现并发问题。

 6、事实不可变对象:如果对象在发布后不会被修改,那么对于其他在没有额外同步的情况下安全的访问这些对象的线程来说,安全发布时足够的。所有的安全发布机制都能确保,当对象的引用对所有访问改对象的线程可见时,对象发布时的状态对于所有线程也将是可见的,并且如果对象状态不会再改变,那么就足以确保任何访问都是安全的。如果对象从技术上来看是可变的,但他的状态在发布hou不会再改变,那么把这种对象称之为事实不可变对象。例如:date本身是可变的,但如果将它作为不可变对象使用,那么在多线程之间共享Date对象时,就可以省去对锁的使用,假设需要维护一个Map对象,其中保存了每位用户的最近登录时间:

public Map<String, Date> lastLogin = Collections.synchronizedMap(new HaspMap<String, Date>());

如果Date对象的值被放入Map后就不会改变,那么synchronizedMap中的同步机制就足以使Date值被安全的发布,并且在访问这些Date值时候不需要额外的同步。

可变对象:如果对象在构造后可以修改,那么安全发布只能确保发布当时状态的可见性,对于可变对象,不仅在发布对象时需要使用同步,而且在每次对象访问时同样需要使用同步来确保后续修改操作的可见性,要安全的共享可变对象,这些对象就必须被安全的发布,并且必须是线程安全的或者被某个锁保护起来。

安全的共享对象:当获得对象的一个引用时,你需要知道在这个引用上可以执行那些操作,在使用它之前是否需要获得一个锁,是否可以修改它的状态,或者只能读取它,许多并发错误都是由于没有理解共享对象的这些既定规则导致的。当发布一个对象时,必须明确的说明对象的访问方式。

在并发程序中使用和共享对象时,可以使用一些实用的策略:线程封闭。线程封闭的对象只能由一个线程拥有,对象被封闭在该线程中,并且只能由这个线程修改。

只读共享:在没有额外同步的情况下,共享的只读对象可以由多个线程并发访问,但是任何都不能修改它,共享的只读对象包括不可变对象和事实不可变对象。

线程安全共享:线程安全的对象在其内部实现同步,因此多个线程可以通过对象的公有接口来进行访问而不需要进一步的同步。

posted @ 2020-03-01 15:55  Hotz亚军  阅读(214)  评论(0)    收藏  举报