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

不可变对象

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

  • 对象创建以后其状态就不能修改
  • 对象所有域都是final类型
  • 对象是正确创建的(在对象创建期间,this引用没有逸出)

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

  • 将类声明成final类型,使其不可以被继承
  • 将所有的成员设置成私有的,使其他的类和对象不能直接访问这些成员
  • 对变量不提供set方法
  • 将所有可变的成员声明为final,这样只能对他们赋值一次
  • 通过构造器初始化所有成员,进行深度拷贝
  • 在get方法中,不直接返回对象本身,而是克隆对象,返回对象的拷贝

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

  • 修饰类:不能被继承(final类中的所有方法都会被隐式的声明为final方法)
  • 修饰方法:1、锁定方法不被继承类修改;2、提升效率(private方法被隐式修饰为final方法)
  • 修饰变量:基本数据类型变量(初始化之后不能修改)、引用类型变量(初始化之后不能再修改其引用)

其他的不可变对象的创建

  • Collections.unmodifiableMap 创建完以后不允许被修改 源码
 /**
    * 初始化的时候将传进来的map赋值给一个final类型的map,然后将所有会修改的方法直接抛出UnsupportedOperationException异常
     * Returns an unmodifiable view of the specified map.  This method
     * allows modules to provide users with "read-only" access to internal
     * maps.  Query operations on the returned map "read through"
     * to the specified map, and attempts to modify the returned
     * map, whether direct or via its collection views, result in an
     * <tt>UnsupportedOperationException</tt>.<p>
     *
     * The returned map will be serializable if the specified map
     * is serializable.
     *
     * @param <K> the class of the map keys
     * @param <V> the class of the map values
     * @param  m the map for which an unmodifiable view is to be returned.
     * @return an unmodifiable view of the specified map.
     */
    public static <K,V> Map<K,V> unmodifiableMap(Map<? extends K, ? extends V> m) {
        return new UnmodifiableMap<>(m);
    }

    /**
     * @serial include
     */
    private static class UnmodifiableMap<K,V> implements Map<K,V>, Serializable {
        private static final long serialVersionUID = -1034234728574286014L;

        private final Map<? extends K, ? extends V> m;

        UnmodifiableMap(Map<? extends K, ? extends V> m) {
            if (m==null)
                throw new NullPointerException();
            this.m = m;
        }

        public int size()                        {return m.size();}
        public boolean isEmpty()                 {return m.isEmpty();}
        public boolean containsKey(Object key)   {return m.containsKey(key);}
        public boolean containsValue(Object val) {return m.containsValue(val);}
        public V get(Object key)                 {return m.get(key);}

        public V put(K key, V value) {
            throw new UnsupportedOperationException();
        }
        public V remove(Object key) {
            throw new UnsupportedOperationException();
        }
复制代码

测试

@ThreadSafe
public class ImmutableExample1 {
    private static Map<Integer,Integer> map = Maps.newHashMap();

    static {
        map.put(1,2);
        map = Collections.unmodifiableMap(map);
    }

    public static void main(String[] args) {
        //Exception in thread "main" java.lang.UnsupportedOperationException
        //	at java.util.Collections$UnmodifiableMap.put(Collections.java:1457)
        //	at com.gwf.concurrency.example.immutable.ImmutableExample1.main(ImmutableExample1.java:21)
        map.put(1,3);
    }
    
}
复制代码
  • Guava:Immutablexxx 源码
// ImmutableList
public static <E> ImmutableList<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10, E e11) {
        return construct(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11);
    }

// 超过12个元素,则声明为一个数组
    public static <E> ImmutableList<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, 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);
        return construct(array);
    }

private static <E> ImmutableList<E> construct(Object... elements) {
        for(int i = 0; i < elements.length; ++i) {
            ObjectArrays.checkElementNotNull(elements[i], i);
        }

        return new RegularImmutableList(elements);
    }
复制代码

实例

@ThreadSafe
public class ImmutableExample2 {
    private final static List<Integer> list = ImmutableList.of(1,2,3);
    private final static ImmutableSet set = ImmutableSet.copyOf(list);
    // 奇数位参数为key,偶数位参数为value
    private final static ImmutableMap map1 = ImmutableMap.of(1,2,3,5);

    private final static ImmutableMap<Integer,Integer> map2 = ImmutableMap.<Integer,Integer>builder()
            .put(1,2).put(3,4).build();

    public static void 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 static ThreadLocal<Long> requestHolder = new ThreadLocal<>();

    /**
     * 添加数据
     * 在filter里将登录用户信息存入ThreadLocal
     * 如果不使用ThreadLocal,我们会需要将request一直透传
     * @param id
     */
    public static void add(Long id){
        // ThreadLocal 内部维护一个map,key为当前线程名,value为当前set的变量
        requestHolder.set(id);
    }

    /**
     * 获取数据
     * @return
     */
    public static Long getId(){
        return requestHolder.get();
    }

    /**
     * 移除变量信息
     * 如果不移除,那么变量不会释放掉,会造成内存泄漏
     * 在接口处理完以后进行处理(interceptor)
     */
    public static void remove(){
        requestHolder.remove();
    }
}

复制代码

线程不安全的类与写法

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

/**
*  由于StringBuffer 加了 synchronized 所以性能会下降很多
* 所以在堆栈封闭等线程安全的环境下应该首先选用StringBuilder
*/
@Override
    public synchronized StringBuffer append(Object obj) {
        toStringCache = null;
        super.append(String.valueOf(obj));
        return this;
    }

复制代码

2.SimpleDateFormat SimpleDateFormat 在多线程共享使用的时候回抛出转换异常,应该才用堆栈封闭在每次调用方法的时候在方法里创建一个SimpleDateFormat 另一种方式是使用joda-time的DateTimeFormatter(推荐使用)

<dependency>
   		<groupId>joda-time</groupId>
   		<artifactId>joda-time</artifactId>
   		<version>2.9</version>
   	</dependency>
复制代码
    private static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyyMMdd");

DateTime.parse("20180320",dateTimeFormatter).toDate();
复制代码

3.ArrayList,HashMap,HashSet等Collections

4.先检查再执行

// 非原子性
if(condition(a)){
  handle(a);
}
复制代码

线程安全-同步容器

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

vector的所有方法都是有synchronized关键字保护的 stack继承了vector,并且提供了栈操作(先进后出) hashtable也是由synchronized关键字保护

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

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

/**
 * 并发测试
 * 同步容器不一定线程安全
 * @author gaowenfeng
 */
@Slf4j
@NotThreadSafe
public class VectorExample2 {

    /** 请求总数 */
    public static int clientTotal = 5000;
    /** 同时并发执行的线程数 */
    public static int threadTotal = 50;

    public static List<Integer> list = new Vector<>();

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            list.add(i);
        }
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                list.remove(i);
            }
        });

        Thread thread2 = new Thread(() -> {
            // thread2想获取i=9的元素的时候,thread1将i=9的元素移除了,导致数组越界
            for (int i = 0; i < 10; i++) {
                list.get(i);
            }
        });

        thread1.start();
        thread2.start();
    }

}
复制代码

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

public class VectorExample3 {

    // java.util.ConcurrentModificationException
    // 在遍历的同时进行了删除的操作,导致抛出了并发修改的异常
    private static void test1(Vector<Integer> v1) { // foreach
        for(Integer i : v1) {
            if (i.equals(3)) {
                v1.remove(i);
            }
        }
    }

    // java.util.ConcurrentModificationException
    private static void test2(Vector<Integer> v1) { // iterator
        Iterator<Integer> iterator = v1.iterator();
        while (iterator.hasNext()) {
            Integer i = iterator.next();
            if (i.equals(3)) {
                v1.remove(i);
            }
        }
    }

    // success
    private static void test3(Vector<Integer> v1) { // for
        for (int i = 0; i < v1.size(); i++) {
            if (v1.get(i).equals(3)) {
                v1.remove(i);
            }
        }
    }

    public static void main(String[] args) {

        Vector<Integer> vector = new Vector<>();
        vector.add(1);
        vector.add(2);
        vector.add(3);
        test1(vector);
    }
}
复制代码

并发容器-J.U.C(java.util.concurrent)

  • ArrayList->CopyOnWriteArrayList

写操作时复制:先从原有的数组里面拷贝一份出来,然后在新的数组上做写操作,写完之后再讲原来的数组指向到新的数组,整个的add操作都是在锁的保护下进行的,这么做主要是为了避免在并发进行add操作的时候,复制出多个副本出来,把数据搞乱了,导致最终数组中的数据不是我们期望的 缺点: 1.由于需要复制,所以可能会消耗比较多的内存,导致yang gc和full gc 2.不能用于实时读的操作,因为拷贝数组,写操作需要一定的时间,可能我们get操作获取到的元素还是旧的,虽然CopyOnWriteArrayList能够保证最终的一致性,但是他没法满足我们实时性的要求,因此CopyOnWriteArrayList更适合都读多写少的场景

如果不知道这个数组要放多少元素,做多少add操作,那么要慎用,因为可能会导致浪费很多的内存

设计思想:1.读写分离 2.最终一致性 3.使用时另外开辟空间解决并发冲突

  • HashSet-> CopyOnWriteArraySet;TreeSet->ConcurrentSkipListSet

CopyOnWriteArraySet 线程安全,实现同CopyOnWriteArrayList,适合集合量比较小且只读操作大于可变操作

ConcurrentSkipListSet 是jdk6新增的类,和TreeSet一样支持自然排序,并且在构造的时候自己定义比较器,基于map结合,在多线程环境下,他的contains、add、remove方法都是线程安全的,但是对于批量操作addAll,removeAll等操作并不能保证以原子方式执行,因为他们底层还是调用单元素的操作方法,他们只能保证每一次执行的时候不会被打断,但是不能保证次与次之间执行不会被打断;并且,他不允许使用空元素

HashMap->ConcurrentHashMap;TreeMap->ConcurrentSkipListMap ConcurrentHashMap 不允许控制,在大部分情况下,对map都是读操作,而put,remove操作较少,因此ConcurrentHashMap对读操作做了大量优化,因此这个类具有特别高的并发性,高并发场景下有特别好的表现(后面会有专门的章节介绍ConcurrentHashMap的实现原理) ConcurrentSkipListMap内部使用skiplist,跳表的数据结构实现,在4个线程,1.6个数据的情况下,ConcurrentHashMap是ConcurrentSkipListMap4倍的性能,但是ConcurrentSkipListMap由一些无法比拟的优点 1.ConcurrentSkipListMap的key是优秀的 2.ConcurrentSkipListMap支持更高的并发,他的存取时间和线程数没有关系的,也就是说在数据量一定的情况下并发的线程越多,他的性能越好

总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值