java并发编程实战-第3章-对象的共享

java并发编程实战-第3章-对象的共享

如何共享和发布对象,使其能符合第2章所讨论的多个线程下的安全性访问。2-3章是第4章构建线程安全类

和通过java.util.concurrent类库来构建并发应用程序的重要基础

同步除了实现原子性操作外,另一个重要方面内存可见性

3.1可见性

  In order to ensure visibility of memory writes across threads, you must use synchronization.

  public class NoVisibility {

    private static boolean ready;

    private static int number;

    private static class ReaderThread extends Thread {

        public void run() {

            while (!ready)

                Thread.yield();

            System.out.println(number);

        }

    }

    public static void main(String[] args) {

        new ReaderThread().start();

        number = 42;

        ready = true;

    }

}

在没有同步的情况下,无法保证主线程写入的值对读线程是可见的(虽然如上代码测试几次可能都会输出42

在没有同步的情况下,编译器、处理器以及运行时都可能对执行顺序做调整(重排序)

3.1.1 失效数据

NoVisibility 可能产生失效数据,可能输出错误的结果、可能引起死循环、意料外的异常、被破坏的数据

结构等等

3.1.2 非原子的64位操作

   对于64位的long、double操作。jvm 允许拆分成2个32位的操作。所以在多个线程的读写下即使不考虑数

据失效、也是不安全的。除非使用volatile声明或者用锁保护

   

3.1.3 加锁与可见性

          Locking is not just about mutual exclusion; it is also about memory visibility

3.1.4 volatile

      When a field is declared volatile, the compiler and runtime are put on notice that this 

variable is shared and that operations on it should not be reordered with other memory 

operations.

      Volatile variables are not cached in registers or in caches where they are hidden from 

other processors, so a read of a volatile variable always returns the most recent write by any 

thread

    即:1、不重排序 2、不缓存在寄存器或其他处理器不可见的地方

    

    

    volatile只能保证可见性,但不保证++count 的原子性。(原子变量提供了读-写-改的原子操作,通常

是一种更好的volatile变量)

   You can use volatile variables only when all the following criteria are met:(使用volatiel

,以下3个条件都要买足)

Writes to the variable do not depend on its current value, or you can 

ensure that only a single thread ever updates the value;

The variable does not participate in invariants with other state 

variables; and

Locking is not required for any other reason while the variable is 

being accessed.

    

    

    适用场景:

     1、作为状态标志

     volatile boolean asleep;

      ...

    while (!asleep)

        countSomeSheep();

        

3.2 发布和逸出

    发布:使对象在当前代码之外仍旧可用。比如存储对象的应用使其可以别的代码可以访问、通过非私有

方法返回、将引用传递给其他类的方法

    逸出:An object that is published when it should not have been is said to have escaped.

    

    逸出例子:

    例子1:共有静态的变量

   Listing 3.5. Publishing an Object.

public static Set<Secret> knownSecrets;

public void initialize() {

   knownSecrets = new HashSet<Secret>();

}

    如上共有静态的变量 knownSecrets,就是在没有initialize()完成,其它线程也能访问

    

    例子2:使内部的可变状态逸出

    class UnsafeStates {

    private String[] states = new String[] {

        "AK", "AL" ...

    };

    public String[] getStates() { return states; }

    }

   

    

    例子3:在public构造函数中发布内部类(发布的EventListener含有ThisEscape的this的隐式引用,但

ThisEscape确没有构造完成)

    public class ThisEscape {

    public ThisEscape(EventSource source) {

        source.registerListener(

            new EventListener() {

                public void onEvent(Event e) {

                    doSomething(e);

                }

            });

     }

   }

   

   需要当且仅当构造函数返回时,对象才处于可预测的和一致性状态

   所以,可以如下正确的发布:

   使用工厂方法防止this引用在构造过程中逸出

   

  public class SafeListener {

   private final EventListener listener;

   private SafeListener() {

       listener = new EventListener() {

           public void onEvent(Event e) {

               doSomething(e);

           }

       };

   }

   public static SafeListener newInstance(EventSource source) {

       SafeListener safe = new SafeListener();

       source.registerListener(safe.listener);

       return safe;

   }

}

3.3 线程封闭

    

    如果仅在单线程里访问数据,则不需要同步。

    应用例子:

    swing中大量使用线程封闭技术,   swing常见错误,其他线程使用了该被封闭的对象

    jdbc的Connection对象,web服务器的请求线程从连接池中获取一个connection对象,直到使用完后返

回给连接池。在此期间,不会把connection分配给其他线程

    ThreadLocal :struts2等框架,把请求参数通过ThreadLocal放在封闭在自己线程中

    

    方式有Ad-hoc、栈封闭、和ThreadLocal

    

    (5.3.2 的串行线程封闭是否属于Ad-hoc呢)

    

3.3.1 Ad-hoc 线程封闭(Ad-hoc拉丁语 for this 。专门的、特别设定的)

     封闭的职责完全由程序来负责,很脆弱

3.3.2 栈封闭

3.3.3 ThreadLocal类

      例子:

       private static ThreadLocal<Connection> connectionHolder

   = new ThreadLocal<Connection>() {

       public Connection initialValue() {

           return DriverManager.getConnection(DB_URL);

       }

   };

public static Connection getConnection() {

   return connectionHolder.get();

}

   

         

         

         

      可以看成把ThreadLocal(T)看成Map<Thread,T>,但不仅如此,提供了当线程停止后,T会被作为垃

圾回收

      应用程序框架大量使用ThreadLocal。J2EE容器需要将一个事务上下文( Transaction Context )与

某个执行的线程关联起来

      开发人员经常滥用ThreadLocal ,ThreadLocal 类似全局变量,它能降低代码的重用性。引入耦合。

操作要额外小心

      

      扩展:

      spring DAO模板等底层技术,ThreadLocal是必须攻克的

      

      

      

3.4 不变性

    3.4.1 Final域

    3.4.2 示例:使用Volatile类型发布不可变对象

3.5 安全发布

   Immutable objects can be used safely by any thread without additional synchronization, even 

when synchronization is not used to publish them.

   

3.5.3 安全发布常用模式

      可变对象必须通过安全的方式发布的,发布的

     To publish an object safely, both the reference to the object and the object's state must 

be made visible to other threads at the same time. A properly constructed object can be safely 

published by:

Initializing an object reference from a static initializer;(

怎么理解?

public static Holder holder = new Holder(42);

Static initializers are executed by the JVM at class initialization 

time; because of internal synchronization in the JVM, this mechanism is guaranteed to safely 

publish any objects initialized in this way [JLS 12.4.2].

      )

Storing a reference to it into a volatile field or AtomicReference;

Storing a reference to it into a final field of a properly constructed 

object; or

Storing a reference to it into a field that is properly guarded by a 

lock.

3.5.4 事实不可变对象

      技术上可变,事实上不可变

      

3.5.5  可变对象

The publication requirements for an object depend on its mutability:

Immutable objects can be published through any mechanism;

Effectively immutable objects must be safely published;

Mutable objects must be safely published, and must be either 

threadsafe or guarded by a lock(3.5.3介绍的安全发布模式)

3.5.6  安全的共享对象,即最后如下总结

总结:

  

  The most useful policies for using and sharing objects in a concurrent program are:

1、线程封闭 Thread-confined. A thread-confined object is owned exclusively by and 

confined to one thread, and can be modifled by its owning thread.

2、只读共享 Shared read-only. A shared read-only object can be accessed concurrently 

by multiple threads without additional synchronization, but cannot be modified by any thread. 

Shared read-only objects include immutable and effectively immutable objects.

3、线程安全的对象的共享 Shared thread-safe. A thread-safe object performs 

synchronization internally, so multiple threads can freely access it through its public 

interface without further synchronization.

4、保护对象 Guarded. A guarded object can be accessed only with a specific lock held. 

Guarded objects include those that are encapsulated within other thread-safe objects and 

published objects that are known to be guarded by a specific lock.

猜你喜欢

转载自zhouchaofei2010.iteye.com/blog/2243277