• java并发编程笔记(五)——线程安全策略


    java并发编程笔记(五)——线程安全策略

    不可变得对象

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

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

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

    • 修饰类:不能被继承
    • 修饰方法:1、锁定方法不被继承类修改;2、效率,目前近期版本已经将私有方法默认设置为final,final能够使方法转为内嵌调用。
    • 修饰变量:基本数据类型变量、引用类型变量

    定义不可变对象的其他方法:

    • Collections.unmodifiablexxx:Collection、List、Set、Map... //这种方法直接禁止对集合进行任何操作,否则会报错。
    public class ImmutableExample2 {
    
        private static Map<Integer, Integer> map = Maps.newHashMap();
    
        static {
            map.put(1, 2);
            map.put(3, 4);
            map.put(5, 6);
            map = Collections.unmodifiableMap(map);
        }
    
        public static void main(String[] args) {
            map.put(1, 3);    //抛异常
            log.info("{}", map.get(1));
        }
    
    }
    
    • Guava:Immutablexxx:Collection、List、Set、Map...

      如果改变集合,则会抛异常

      public class ImmutableExample3 {
      
          private final static ImmutableList<Integer> list = ImmutableList.of(1, 2, 3);
      
          private final static ImmutableSet set = ImmutableSet.copyOf(list);
      
          private final static ImmutableMap<Integer, Integer> map = ImmutableMap.of(1, 2, 3, 4);
      
          private final static ImmutableMap<Integer, Integer> map2 = ImmutableMap.<Integer, Integer>builder()
                  .put(1, 2).put(3, 4).put(5, 6).build();
      
      
          public static void main(String[] args) {
              System.out.println(map2.get(3)); 
          }
      }
      

    线程封闭

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

    2、堆栈封闭:通过设置局部变量,无并发问题

    3、ThreadLocl线程封闭:特别好的封闭方法

    正产情况下,每一个请求对服务器来说,都是一个单独的线程。

    如果不适用,ThreadLock,下层代码想要获取上层代码的某些值,需要将值不端的往下传,造成代码臃肿。

    在使用ThreadLocal的时候,注意要删除里边的东西,否则会造成内存泄漏,因为ThreadLock里边的东西只有在重启服务器的时候会清空。

    应用:jdbc的Connection

    线程不安全类与写法

    不安全的类

    StringBuffer与StringBuilder

    stringBuffer虽然线程安全,但是内部使用了synchronize关键字,性能有损耗

    如何本身是单线程环境或者不会涉及线程安全问题,还是以StringBuilder为主

    SimpleDataFormat与DataTimeFormatter

    SimpleDataFormat不能轻易作为全局变量在多线程环境下使用,它是线程不安全的;

    DataTimeFormatter不是JDK自带的,需要额外引入一个jar包(joda-time)

    在实际使用中:推荐使用DataTimeFormatter

    ArrayList、HashSet|、HashMap等Collections

    ArrayList:线程不安全

    HashSet:线程不安全

    HashMap:线程不安全

    不安全的写法

    先检查再执行:if(condition(a){handle(a)})

    当两个线程同时执行到if语块的时候,可能会发生并发情况,造成handle重复执行,这种情况下应考虑加锁。

    线程安全的同步容器(性能不太好)

    • ArrayList -> Vector(不一定是完全线程安全)、Stack
    • HashMap -> HashTable(key,value不能为null)
    • Collections.synchronizedXXX(List,Set,Map)

    线程安全的并发容器J.U.C

    J.u.c(java.util.concurrent)

    1、ArrayList -> CopyOnWriteArrayList

    CopyOnWriteArrayList:写操作的时候,复制,当有新元素添加到list中的时候,它先从原有的数组里边拷贝一份出来,然后在新的数组上进行写操作,然后再将原来的数组指向新的数组。

    整个的add操作都是在锁的保护下进行的,主要是为了避免在add进行复制数组的时候,复制出多个副本导致数据混乱。

    缺点:

    • 在写操作的时候需要复制数组,所以比较消耗内存,如果元素的内容比较多,可能会导致yong GC和full GC
    • 不能用于实时读的场景,因为集合拷贝数组,新增元素都需要时间,可保证数据安全,但无法保证实时性,更适合读多写少的场景

    ​ 如果在使用的时候无法知道所存储数据量的大小,还是要慎用。

    该集合的设计思想:

    • 读写分离;
    • 最终一致性;
    • 使用时另外开辟空间,通过这种方式能够避免并发冲突。

    2、HashSet、TreeSet -> CopyOnWriteArraySet、ConcurrentSkipListSet

    CopyOnWriteArraySet(对应HashSet)

    这个集合是线程安全的,底层用了上述的CopyOnWriteArrayList,一次这个集合也是适合数据大小比较小的set集合,所以写操作的开销也很大。

    ConcurrentSkipListSet(对应TreeSet)

    支持自然排序,支持在构造的时候自定义比较器,基于Map集合,可变操作都是现成安全的。但是对于批量操作比如addAll(),removeAll()不能支持原子操作,不支持空元素。

    3、HashMap、TreeMap->ConcurrentHashMap、ConcurrentSkipListMap

    ConcurrentHashMap不允许空值

    ConcurrentSkipListMap

    key是有序的,支持更高的并发,

    JUC总结

    • 线程限制:一个被线程限制的对象,由线程独占,并且只能被占有它的线程修改
    • 共享制度:一个共享只读的对象,在没有额外同步的情况下,可以被多个线程并发访问,但是任何线程都不能修改它。
    • 线程安全对象:一个线程安全的对象或者容器,在内部通过同步机制来保证线程安全,所以其他线程无需额外的同步就可以通过公共接口随意访问它。
    • 被守护对象,被守护对象只能通过获取特定的锁来访问
  • 相关阅读:
    主线程到子线程的相互切换
    IOS通过OTA部署App
    IOS应用之间调用
    静态库详解
    ObjectC的函数调用机制详解消息
    iOS6新特征:参考资料和示例汇总
    杭电acm2025
    杭电acm2051
    杭电acm1009
    杭电acm2099
  • 原文地址:https://www.cnblogs.com/xujie09/p/11694178.html
Copyright © 2020-2023  润新知