美文网首页
慕课网高并发实战(六)- 线程安全策略

慕课网高并发实战(六)- 线程安全策略

作者: 景行lau | 来源:发表于2018-03-29 09:35 被阅读0次

    不可变对象

    不可变对象需要满足的条件

    对象创建以后其状态就不能修改

    对象所有域都是final类型

    对象是正确创建的(在对象创建期间,this引用没有逸出)

    创建不可变对象的方式(参考String类型)

    将类声明成final类型,使其不可以被继承

    将所有的成员设置成私有的,使其他的类和对象不能直接访问这些成员

    对变量不提供set方法

    将所有可变的成员声明为final,这样只能对他们赋值一次

    通过构造器初始化所有成员,进行深度拷贝

    在get方法中,不直接返回对象本身,而是克隆对象,返回对象的拷贝

    final关键字:类、方法、变量

    修饰类:不能被继承(final类中的所有方法都会被隐式的声明为final方法)

    修饰方法:1、锁定方法不被继承类修改;2、提升效率(private方法被隐式修饰为final方法)

    修饰变量:基本数据类型变量(初始化之后不能修改)、引用类型变量(初始化之后不能再修改其引用)

    其他的不可变对象的创建

    Collections.unmodifiableMap

        创建完以后不允许被修改源码

     /**

        *初始化的时候将传进来的map赋值给一个final类型的map,然后将所有会修改的方法直接抛出UnsupportedOperationException异常

         * Returns anunmodifiable view of the specified map. This method

         * allowsmodules to provide users with "read-only" access to internal

         * maps.  Query operations on the returned map"read through"

         * to thespecified map, and attempts to modify the returned

         * map, whetherdirect or via its collection views, result in an

         *UnsupportedOperationException.

         *

         * The returnedmap will be serializable if the specified map

         * isserializable.

         *

         * @param the class of the map keys

         * @param the class of the map values

         * @param  m the map for which an unmodifiable view isto be returned.

         * @return anunmodifiable view of the specified map.

         */

        public static Map unmodifiableMap(Mapm) {

            return newUnmodifiableMap<>(m);

        }

        /**

         * @serialinclude

         */

        private static classUnmodifiableMap implements Map, Serializable {

            private staticfinal long serialVersionUID = -1034234728574286014L;

            private finalMap m;

           UnmodifiableMap(Map m) {

                if (m==null)

                    thrownew NullPointerException();

                this.m= m;

            }

            public int size()                        {return m.size();}

            public booleanisEmpty()                 {returnm.isEmpty();}

            public booleancontainsKey(Object key)   {returnm.containsKey(key);}

            public booleancontainsValue(Object val) {return m.containsValue(val);}

            public V get(Objectkey)                 {return m.get(key);}

            public V put(Kkey, V value) {

                throw newUnsupportedOperationException();

            }

            public V remove(Objectkey) {

                throw newUnsupportedOperationException();

            }

    测试

    @ThreadSafe

    public class ImmutableExample1 {

        private staticMap map = Maps.newHashMap();

        static {

            map.put(1,2);

            map =Collections.unmodifiableMap(map);

        }

        public static voidmain(String[] args) {

            //Exceptionin thread "main" java.lang.UnsupportedOperationException

            //  atjava.util.Collections$UnmodifiableMap.put(Collections.java:1457)

            // atcom.gwf.concurrency.example.immutable.ImmutableExample1.main(ImmutableExample1.java:21)

            map.put(1,3);

        }

    }

    Guava:Immutablexxx

    源码

    // ImmutableList

    public static ImmutableList of(E e1, Ee2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10, E e11) {

            returnconstruct(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11);

        }

    // 超过12个元素,则声明为一个数组

        public static ImmutableList of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, Ee8, E e9, E e10, E e11, E e12, E... others) {

            Object[] array = new Object[12 +others.length];

            array[0] =e1;

            array[1] =e2;

            array[2] =e3;

            array[3] =e4;

            array[4] =e5;

            array[5] =e6;

            array[6] =e7;

            array[7] =e8;

            array[8] =e9;

            array[9] =e10;

            array[10] =e11;

            array[11] =e12;

           System.arraycopy(others, 0, array, 12, others.length);

            returnconstruct(array);

        }

    private static ImmutableListconstruct(Object... elements) {

            for(int i =0; i < elements.length; ++i) {

               ObjectArrays.checkElementNotNull(elements[i], i);

            }

            return newRegularImmutableList(elements);

        }

    实例

    @ThreadSafe

    public class ImmutableExample2 {

        private final staticList list = ImmutableList.of(1,2,3);

        private final staticImmutableSet set = ImmutableSet.copyOf(list);

        //奇数位参数为key,偶数位参数为value

        private final staticImmutableMap map1 = ImmutableMap.of(1,2,3,5);

        private final staticImmutableMap map2 =ImmutableMap.builder()

                .put(1,2).put(3,4).build();

        public staticvoid main(String[] args) {

            //执行都会跑出 UnsupportedOperationException异常

            //但是使用ImmutableXXX声明会直接在编译的时候就告诉你这个方法已经被废弃

            list.add(5);

            set.add(6);

            map1.put(1,2);

            map2.put(3,4);

        }

    }

    线程封闭

    把对象封装到一个线程里,只有这个线程能看到这个对象

    实现线程封闭

    Ad-hoc 线程封闭:程序控制实现,最糟糕,忽略

    堆栈封闭:局部变量,无并发问题

    ThreadLocal 线程封闭:特别好的封闭方法

    ThreadLocal 实例保存登录用户信息(具体的业务场景,和拦截器的使用就不赘述了,大家可以购买课程详细学习)

    public class RequestHolder {

        private final staticThreadLocal requestHolder = new ThreadLocal<>();

        /**

         *添加数据

         *在filter里将登录用户信息存入ThreadLocal

         *如果不使用ThreadLocal,我们会需要将request一直透传

         * @param id

         */

        public static voidadd(Long id){

            //ThreadLocal内部维护一个map,key为当前线程名,value为当前set的变量

           requestHolder.set(id);

        }

        /**

         *获取数据

         * @return

         */

        public staticLong getId(){

            returnrequestHolder.get();

        }

        /**

         *移除变量信息

         *如果不移除,那么变量不会释放掉,会造成内存泄漏

         *在接口处理完以后进行处理(interceptor)

         */

        public static voidremove(){

           requestHolder.remove();

        }

    }

    线程不安全的类与写法

    1.StringBuilder 线程不安全,StringBuffer线程安全原因:StringBuffer几乎所有的方法都加了synchronized关键字

    /**

    *  由于StringBuffer 加了 synchronized 所以性能会下降很多

    * 所以在堆栈封闭等线程安全的环境下应该首先选用StringBuilder

    在方法内部,定义局部变量是封闭的,只有一个线程操作变量,是线程安全的。

    */

    @Override

        public synchronizedStringBuffer append(Object obj) {

           toStringCache = null;

            super.append(String.valueOf(obj));

            return this;

        }

    2.SimpleDateFormat

    SimpleDateFormat

    在多线程共享使用的时候回抛出转换异常,应该才用堆栈封闭在每次调用方法的时候在方法里创建一个SimpleDateFormat

    另一种方式是使用joda-time的DateTimeFormatter(推荐使用)

        private staticDateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyyMMdd");

    DateTime.parse("20180320",dateTimeFormatter).toDate();

    3.ArrayList,HashMap,HashSet等Collections

    4.先检查再执行

    // 非原子性

    if(condition(a)){

      handle(a);

    }

    线程安全-同步容器

    1.同一接口,不同实现的线程安全类


    第一类

    vector的所有方法都是有synchronized关键字保护的。vector 实现了list

    stack

    继承了vector,并且提供了栈操作(先进后出)

    hashtable

    也是由synchronized关键字保护

    2. Collections.synchronizedXXX (list,set,map)

    注意:1.同步容器并不一定线程安全

    /**

     *并发测试

     *同步容器不一定线程安全

     * @authorgaowenfeng

     */

    @Slf4j

    @NotThreadSafe

    public class VectorExample2 {

        /**请求总数*/

        public static intclientTotal = 5000;

        /**同时并发执行的线程数*/

        public static intthreadTotal = 50;

        public staticList list = new Vector<>();

        public static voidmain(String[] args) throws InterruptedException {

            for (int i= 0; i < 10; i++) {

                list.add(i);

            }

            Threadthread1 = new Thread(() -> {

                for (inti = 0; i < 10; i++) {

                    list.remove(i);

                }

            });

            Threadthread2 = new Thread(() -> {

                //thread2想获取i=9的元素的时候,thread1将i=9的元素移除了,导致数组越界

                for (inti = 0; i < 10; i++) {

                    list.get(i);

                }

            });

           thread1.start();

           thread2.start();

        }

    }

    注意:2.在foreach或迭代器遍历的过程中不要做删除操作,应该先标记,然后最后再统一删除

    public class VectorExample3 {

        //java.util.ConcurrentModificationException

        //在遍历的同时进行了删除的操作,导致抛出了并发修改的异常

        private static voidtest1(Vector v1) { // foreach

            for(Integeri : v1) {

                if(i.equals(3)) {

                   v1.remove(i);

                }

            }

        }

        //java.util.ConcurrentModificationException

        private static voidtest2(Vector v1) { // iterator

           Iterator iterator = v1.iterator();

            while(iterator.hasNext()) {

                Integeri = iterator.next();

                if(i.equals(3)) {

                   v1.remove(i);

                }

            }

        }

        // success

        private static voidtest3(Vector v1) { // for

            for (int i= 0; i < v1.size(); i++) {

                if(v1.get(i).equals(3)) {

                   v1.remove(i);

                }

            }

        }

        public static voidmain(String[] args) {

           Vector vector = new Vector<>();

            vector.add(1);

            vector.add(2);

            vector.add(3);

            test1(vector);

        }

    }

    CopyOnWriteArraySet:  线程安全的,底层实现是CopyOnWriteArrayList,只读操作远大于可读操作,迭代器不支持remove 操作

    ConcurrentSkipListSet:支持自然排序,基于map集合,set(),add(), remove() 都是线程安全的  对于批量操作addAll

    removeAll() 方法是线程不安全的

    作者:Meet相识_bfa5

    链接:https://www.jianshu.com/p/7a7f13f091bc

    來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    相关文章

      网友评论

          本文标题:慕课网高并发实战(六)- 线程安全策略

          本文链接:https://www.haomeiwen.com/subject/ulfpcftx.html