zoukankan      html  css  js  c++  java
  • java并发程序和共享对象实用策略

    java并发程序和共享对象实用策略

    在并发程序中使用和共享对象时,可以使用一些实用的策略,包括:

    1. 线程封闭
    2. 只读共享。共享的只读对象可以由多个线程并发访问,但任何线程都不能修改它。共享的只读对象包括不可变对象和事实不可变对象
    3. 线程安全共享。线程安全地对象在器内部实现同步。
    4. 保护对象。被保护的对象只能通过持有特定的锁来方访问。

    线程封闭

    当访问共享的可变数据时,通常需要使用同步。一种避免使用同步的方式就是不共享数据。如果仅在单线程内访问数据,就不需要同步。这种技术被称为线程封闭,它是实现线程安全性的最简单方式之一。

    Ad-hoc线程封闭指的是,维护线程封闭性的职责完全由程序实现来承担。

    当决定使用线程封闭技术时,通常是因为要将某个特定的子系统实现为一个单线程子系统。在volatile变量上存在一种特殊的线程封闭,只要你能确保只有单个线程对共享的volatile变量执行写入操作,那么就可以安全地在这些共享的volatile变量上执行“读取-修改-写入”操作。在这种情况下,相当于将修改操作封闭在单个线程中以防止发生竞态条件,并且volatile变量的可见性保证还确保了其他线程能看到的最新值。

    栈封闭是一种线程封闭的特例,在栈封闭中,只能通过局部变量才能访问对象。局部变量的固有属性之一就是封闭在执行线程中。它们位于执行线程的栈中,其他线程无法访问这个栈。在下面的例子中,由于任何方法都无法获得对基本类型的引用,因此Java语言的这种语义就确保了基本类型的局部变量始终封闭在线程内。

    public int loadTheArk(Collection<Animal> candidates){
        SortedSet<Animal> animals;
        int numPirs = 0;
        Animal candidate = null;
        
        //animals被封闭在方法中,不要使它们逸出!
        animals = new TreeSet<Animal>(new SpeciesGenderComparator());
        animals.addAll(candidates);
        for(Animal a : animals){
            if(candidate == null || !candidate.isPotentialMate(a))
                candidate =a;
            else{
                ark.load(new AnimalPair(candidate,a));
                ++numPairs;
                candidate = null;
            }
        }
        return numPairs;
    }
    

    在维持对象引用的栈封闭时,程序员需要多做一些工作以确保引用的对象不会逸出。

    ThreadLocal类通常用于防止对可变的单实例变量(Singleton)或全局变量进行共享。例如通过将JDBC的连接保存到ThreaLocal对象中,每个线程都会拥有属于自己的连接

    private static ThreadLocal<Connection> connectionHolder
    	=new ThreadLocal<Connection>(){
            public Connection initialValue(){
    			return DriverManager.getConnection(DB_URL);
            }
    };
    
    public static Connection getConnection(){
        return connectionHolder.get();
    }
    

    当某个频繁执行的操作需要一个临时对象,例如一个缓冲区,而同时又希望避免在每次执行时都重新分配该临时对象,就可以用这个技术。但是,ThreadLocal变量类似于全局变量,它能降低代码的可重用性,并在类之间引入隐含的耦合性,因此在使用时要格外小心。

    不变性

    满足同步需求的另一种方法是使用不可变对象。线程安全性是不可变对象的固有属性之一,它们的不变性条件是由构造函数创建的,只要它们的状态不改变,那么这些不变条件就能得以维持。另一方面,不可变对象不会像这样被恶意代码或者有问题的代码破坏,因此可以安全地共享和发布这些对象,而无须创建保护性的副本。

    当满以下条件时,对象才是不可变的:

    1. 对象创建以后其状态就不能修改。
    2. 对象的所有域都是final类型。
    3. 对象是正确创建的(在对象的创建期间,this引用没有逸出)。
    @Immutable
    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);
        }
    }
    

    使用Volatile类型来发布不可变对象

    @Immutable
    public class OneValueCache {
    
        private final BigInteger lastNumber;
        private final BigInteger[] lastFactors;
    
        public OneValueCache(BigInteger lastNumber, BigInteger[] lastFactors) {
            this.lastNumber = lastNumber;
            this.lastFactors = Arrays.copyOf(lastFactors, lastFactors.length);
        }
    
        public BigInteger[] getFactors(BigInteger integer) {
            if (lastNumber == null || !lastNumber.equals(integer)) {
                return null;
            } else {
                return Arrays.copyOf(lastFactors, lastFactors.length);
            }
        }
    }
    

    使用指向不可变容器对象的volatile类型引用以缓存最新的结果

    @ThreadSafe
    public class VolatileCachedFactorizer implements Servlet {
    
        private volatile OneValueCache cache = new OneValueCache(null, null);
    
        /**
         * 通过使用包含多个状态变量的容器对象来维持不变性的条件,并使用一个volatile类型的引用来确保可见性,
         * 使得Volatile Cache Factorizer 在没有显式地使用锁的情况下仍然是线程安全的
         * @param servletRequest
         * @param servletResponse
         * @throws ServletException
         * @throws IOException
         */
        @Override
        public void service(ServletRequest servletRequest, ServletResponse servletResponse)
                throws ServletException, IOException {
            BigInteger i = extractFromRequest(servletRequest);
            BigInteger[] factors = cache.getFactors(i);
            if (factors == null) {
                factors = factor(i);
                cache = new OneValueCache(i, factors);
            }
            encodeIntoResponse(servletRequest, servletResponse);
        }
    }
    

    安全发布

    要安全地发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见。一个正确构造的对象可以通过以下方式来安全地发布:

    • 在静态初始化函数中初始化一个对象引用。
    • 将对象的引用保存到volatile类型的域或者AtomicReferance对象中。
    • 将对象的引用保存到某个正确构造对象的final类型域中。
    • 将对象的引用保存到一个由锁保护的域中

    尽管javadoc在这个主题上没有给出很清晰的说明,但线程安全库中的容器类提供了以下的安全发布保证:

    • 通过将一个键或者值放入Hashtable、synchronizedMap或者ConcurrentMap中,可以安全地将它发布给任何从这些容器中访问它的线程
    • 通过将某个元素放入Vector、CopyOnWriteArrayList、CopyOnWriteArraySet、synchronizedList或synchronizedSet中,可以将该元素安全地发布到任何从这些容器中访问该元素的线程。
    • 通过将某个元素放入BlockingQueue或者ConcurrentLinkedQueue中,可以将该元素安全地发布到任何从这些队列中访问该元素的线程

    通常,要发布一个静态构造的对象,最简单和最安全的方式是使用静态的初始化器:

    public static Holder hodler= new Holder(42);

    静态初始化器由JVM在类的初始化阶段执行。由于在JVM内部存在着同步机制,因此通过这种方式初始化的任何对象都可以被安全地发布。

    事实不可变对象指的是如果对象从技术上看是可变的,但其状态在发布后不会再改变,那么把这种对象称为事实不可变对象。在这些对象发布后,程序只需要将它们视为不可变对象即可。

  • 相关阅读:
    关于float和double类型能表示的数据范围和精度分析
    P2737 [USACO4.1]麦香牛块Beef McNuggets 数学题 + 放缩思想
    csu 1554: SG Value 思维题
    csu 1551: Longest Increasing Subsequence Again BIT + 思维
    Rasheda And The Zeriba Gym
    cpc,a wonderful concert
    hdu_3308 区间合并
    poj_3667线段树区间合并
    poj_2777线段树+位运算
    poj_3468,线段树成段更新
  • 原文地址:https://www.cnblogs.com/luozhiyun/p/9498850.html
Copyright © 2011-2022 走看看