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

不可变对象

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

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

对象所有域都是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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容