zoukankan      html  css  js  c++  java
  • 《JAVA并发编程实战》示例程序 第三章

     3.1 可见性

     

    程序清单3-1 在没有同步的情况下共享变量(不要这么做)

    /**
     * 主线程和读线程都将访问共享变量:ready 和 number
     * 结果可能
     * 1、 主线程先运行完,读线程后运行:读线程在控制台打印输出42,
     * 2、 读线程先执行完,主线程后执行:读线程在控制台打印输出0
     * 3、 NoVisibility根本无法终止,读线程可能永远都看不到ready的值
     * 4、 重排序(Reordering):读线程看到了主线程中顺序靠后写入的ready的值,但没看到主线程先写入的number的值
     */
    @NotThreadSafe
    public class NoVisibility {
        private static boolean ready;
        private static int number;
        
        //读线程
        private static class ReaderThread extends Thread {
            @Override
            public void run() {
                while(!ready)
                    Thread.yield();
                System.out.println(number);
            }
        }
        
        public static void main(String[] args) {
            new ReaderThread().start();
            number = 42;
            ready = true;
        }
    }

    程序清单3-2  非线程安全的可变整数类

    //mutable 英[ˈmju:təbl] adj.    易变的,性情不定的;
    @NotThreadSafe
    public class MutableInteger {
        
        private int value;
        
        public int getValue() { return value; }
        
        public void setValue(int value) { this.value = value; }
    }

    程序清单3-3  线程安全的可变整数类

    @ThreadSafe
    public class SynchronizedInteger {
        
        @GuardedBy("this")private int value;
        
        public synchronized int getValue() { return value; }
        
        public synchronized void setValue(int value) { this.value = value; }    
    }

    程序清单3-4 数绵羊

    /**
     * volatile通常用作某个操作完成、发生中断或者状态的标志
     * volatile语义不足以确保递增操作(count++)操作的原子性
     * asleep 状态量一旦在某个线程中被更改了,其它线程也应该看到
     * */
    @ThreadSafe
    public class CountSheep {
        
        volatile boolean asleep;
        
        public void countSomeSheep() {
            while(!asleep) {
                for (int i = 0; i < 100; i++) {
                    System.out.println("sheep"+i);
                }
            }
        }
    }

    3.2 发布与逸出

    程序清单3-5   发布一个对象

    /**
     * “发布”一个对象的意思是:是对象能够在当前作用域之外的代码中使用。
     * 1、将一个指向该对象的引用保存到其他代码可以访问的地方
     * 2、在某一个非私有的方法中返回该引用
     * 3、将引用传递到其他类的方法中
     * @author guchunchao
     *
     */
    public class PublicObject {
        
        private static Set<Object> knownSecret;
        
        public void initialize() {
            knownSecret = new HashSet<>(); //将方法体中new的对象发布到了方法体外的变量中
        }
    }

    程序清单3-6  使内部的可变状态逸出(不要这么做)

    /**
     * 按如下方式发布states,任何修改者都能修改这个数组的内容,不安全
     * @author gcc
     */
    public class UnsafeStates {
        private String[] states = {"aaa","bbb","ccc"};
        
        public String[] getStates() { return states; }
    }

    程序清单3-7  隐式地使this引用逸出(不要这么做)

    public class ThisEscape {
        public ThisEscape(EventSource source) {
            source.registerListener(
                new EventListener() {    //发布EventListener时,也隐含的将ThisEscape实例本身:this发布了
                    public void onEvent(Event e) {
                        doSomething(e);
                    }
                }
            );
        }
    }

    程序清单3-8  使用工厂方法来防止this引用在构造过程中逸出

    public class SafeListener {
        private final EventListener listener;
        private ThisEscape(EventSource source) {
            listener =     new EventListener(
                public void onEvent(Event e) {
                    doSomething(e);
                }
            );
        }
        
        public static SafeLisener newInstance(EventSource source) {
            SafeLisener safe = new SafeLisener();
            soure.registerListener(safe.listener);
        }
        
    }

    3.3  线程封闭

    程序清单3-9 基本类型的局部变量与引用变量的线程封闭性

    /**
     * 栈封闭
     * 由于任何方法都无法获得对基本类型的引用,因此基本类型的局部变量始终封闭在线程内
     * @author guchunchao
     *
     */
    public class BasicTypeStackClose {
        //candidate [ˈkændɪdət] 报考者; 申请求职者; 攻读学位者; 最后命运或结局如何已显然可见者;
        public int loadTheArk(Collection<Object> candidates) {
            SortedSet<Object> animals;
            int numPairs = 0;    //方法局部变量,始终在栈中
            Object candidate = null;
            
            animals = new TreeSet<Object>();
            animals.addAll(candidates);
            
            ++numPairs;
            return numPairs;    //即使被return了外界也无法修改。
        }
    }

    程序清单3-10  使用ThreadLocal来维持线程封闭性

    /**
     * ThreadLocal对象通常用于防止对可变的、非线程安全的单实例变量(Singleon)或全局变量进行共享。
     * ThreadLocal的get与set等访问接口或方法为每个使用该变量的线程都存有一份独立的副本,因此get总是能返回当前线程在调用set时设置的新值。
     * 
     * 单线程应用程序中维持一个全局的数据库连接,并在程序启动时初始化,从而避免在调用每个方法时都要传递一个Connection对象。
     * 
     * 而JDBC的连接对象不一定是线程安全的,因此当多线程应用程序在没有协同的情况下使用全局变量时,就不是线程安全的。
     * 将JDBC连接保存到ThreadLocal对象中,每个线程都会拥有属于自己的JDBC连接。
     * 
     * 更多ThreadLocal内容见:http://www.cnblogs.com/dolphin0520/p/3920407.html
     */
    public class ThreadLocalTemplate {
        private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
            public Connection initialValue() {
                return DriverManager.getConnection("DB_URL");  //JDBC的Connection不一定是线程安全的,封装到ThreadLocal中保证每个线程都会拥有自己的JDBC连接对象副本
            }
        };
       
        public static Connection getConnection() {
            return connectionHolder.get();
        }
    }

    3.4  不变性

    不可变对象一定是线程安全的! Why?

    程序清单3-11 在不可变对象基础上构建不可变类

    /**
     * final修饰的类:
     * 从字面意思来理解就是不会发生变化的类,那么是什么不会发生变化呢,其实就是类的状态,也就是不变类的实例一旦被创建,其状态就不会发生变化。
     * 举个例子:
     *         如果人是一个class,那么我们中的每一个都是人这个类的具体的instance,
     *         如果人这个类只有一个状态就是生身父母,那么它就是一个不变类,因为每一个人在出生的那一刹那,生身父母就已经被设置了值,而且终生都不会发生变化。
     * 不变类有什么好处呢?
     *        1) 不变类是线程安全的,由于不变类的状态在创建以后不再发生变化,所以它可以在线程之间共享,而不需要同步。
     *        。。。。*/
    @Immutable    //[ɪˈmju:təbl]不可改变的;
    public final class ThreeStooges {
        
        private final Set<String> stooges = new HashSet<>();//[stu:dʒ]喜剧里的配角或丑角,助手,伙伴;
        
        public ThreeStooges() {
            stooges.add("hello");
            stooges.add("world");
            stooges.add("hello world");
        }
        
        public boolean isStooge(String name) {
            return stooges.contains(name);
        }
    }

    程序清单3-12  对数值及因数分解结果进行缓存的不可变容器类

    /**
     * 对于在访问和更新多个相关变量是出现的竞态条件问题,可以通过将这些变量全部保存在一个不可变对象中来消除。
     * 如果是一个可变的对象,那么就必须使用锁来确保原子性。
     * 如果是一个不可变对象,那么当线程获得了对该对象的饮用后,就不必担心另一个线程会修改对象的状态。
     * 如果要更新这些变量,可以创建一个新的容器对象,但其他使用原有对象的线程仍然会看到对象处于一致的状态。
     * @author guchunchao
     *
     */
    @Immutable
    public class OneValueCache {//存缓存的容器类
        private final BigInteger lastNumber;
        private final BigInteger[] lastFactors;
        public OneValueCache(BigInteger lastNumber, BigInteger[] lastFactors) {
            this.lastNumber = lastNumber;
            this.lastFactors = lastFactors;
        }
        public BigInteger[] getLastFactors(BigInteger i) {
            if(lastNumber == null || lastNumber.equals(i))
                return null;
            else
                return Arrays.copyOf(lastFactors, lastFactors.length);
        }
    }
    
    
    //程序清单3-13  使用指向不可变容器对象的 volatile类型引用以缓存最新结果
    @ThreadSafe
    public class VolatileCacheFactorizer implements Servlet {
        
        private volatile OneValueCache cache = new OneValueCache(null, null);
    
        @Override
        public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
            BigInteger i = extractFromReqest(req);
            BigInteger[] factors = cache.getLastFactors(i);
            if(factors == null) {
                factors = factor(i);
                cache =  new OneValueCache(i, factors);
            }
            encodeIntoResponse(res,factors);
        }
    }

    3.5 安全发布

    程序清单3-14    在没有足够同步的情况下发布对象(不要这么做)

    //不安全的发布
    public Holder holder;
    
    public void initialize() {
         holder = new Holder(42);
    }
    

      

    程序清单3-15 由于未被正确发布,因此这个类可能出现故障

    //不安全的发布
    public class Holder {
        private int n;
        public Holder(int n) {
             this.n = n;  
        }  
       public void assertSanity() {
           if(n != n)
          throw new AssertionError("This Statement is false.");
       }
    }
    

      

      

  • 相关阅读:
    使用Docker搭建svn服务器教程
    VirtualBox上Centos7磁盘扩容
    下载CentOS6.5
    Ubuntu 防火墙常用配置操作(ufw)【适用于 Debian 及其衍生版---Linux Mint、Deepin 等】-转
    诺依/RuoYi开源系统搭建总结
    phpMyAdmin报错#1045
    EasyPHP(php集成环境)下载 v5.4.6官方安装版
    详解----memcache服务端与客户端
    linux 下nginx除了首页404的问题
    linux下禁止root远程登录和添加新用户
  • 原文地址:https://www.cnblogs.com/guchunchao/p/10705137.html
Copyright © 2011-2022 走看看