zoukankan      html  css  js  c++  java
  • Java并发--线程安全策略

     

     

    1 不可变对象

    用不可变对象保证线程安全,是相当于不让线程并发,逃避了并发。

    不可变对象就是指一个类的实例化对象不可变。比如String类的实例

    主要方法有:

      将类声明为final

      将所有成员声明为 private

      对变量不提供 set 方法,将所有可变成员声明为 final,只能赋值一次,通过构造器初始化所有成员,进行深度拷贝

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

    final关键字

    一个类的private方法会隐式的指定为final方法

     final还可以修饰方法的参数,表示该参数在方法中使用时不可以修改。

    这里需要注意的是,final修饰的引用数据类型中其内部数据还是可以被修改的,比如一个final修饰的map,有数据<1,3>,可以被修改为<1,2>,所以并不是线程安全的。需要进行解决。

    Collections.unmodifiableXXX 系列方法,可以解决上述问题,它可以将输入的引用对象变为不可变对象,内部也是不可变的

    Guava:ImmutableXXX系列方法也可以达到相同的效果

    2 线程封闭

    把对象封装到一个线程中,只有这一个线程可以看到这个对象,那么这个对象就不用考虑线程安全问题。

    下面用例子演示ThreadLocal的使用:

    参考博客:面试官再问你 ThreadLocal,你就这样“怼”回去!

      ThreadLocal实现里使用Map来保存的,Key保存线程id,value保存数据。

      假设一个常见的用法,也就是前端往后端传递数据,比如一个用户数据,可能需要从Controller传到Service甚至传到Dao层,多次传递容易出现问题。我们可以利用ThreadLocal + Filter的方式,将数据在交给Controller处理前就保存下来,需要使用的时候进行获取,使用完释放。具体代码:

    首先是保存有ThreadLocal的一个类,用以数据的添加、获取和移除

    public class RequestHolder {
        private final static ThreadLocal<Long> requestHolder = new ThreadLocal<>();
        // 保存数据
        public static void add(Long id) {
            requestHolder.set(id);
        }
    
        // 获取数据
        public static Long getId() {
            return requestHolder.get();
        }
    
        // 移除资源
        public static void remove() {
            requestHolder.remove();
        }
    }

    然后是编写Filter和Inteceptor

    @Slf4j
    public class HttpFilter implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
    
        }
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest)servletRequest;
            log.info("Filter执行,添加线程id:{}到ThreadLocal,请求是{}", Thread.currentThread().getId(), request.getServletPath());
            RequestHolder.add(Thread.currentThread().getId());
            filterChain.doFilter(servletRequest, servletResponse);
        }
    
        @Override
        public void destroy() {
    
        }
    }
    public class HttpInteceptor extends HandlerInterceptorAdapter {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            log.info("preHandle执行");
            return true;
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            log.info("afterCompletion执行");
            RequestHolder.remove();
            return;
        }
    }

    注册Filter和Inteceptor

    @SpringBootApplication
    public class ConcurrencyApplication extends WebMvcConfigurerAdapter {
    
        public static void main(String[] args) {
            SpringApplication.run(ConcurrencyApplication.class, args);
        }
    
        /**
         * 将自己实现的Filter进行注册
         * 需要添加Filter实现类,对应url等信息
         * @return
         */
        @Bean
        public FilterRegistrationBean httpFilter() {
            FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
            // 添加实现类
            filterRegistrationBean.setFilter(new HttpFilter());
            // 添加url
            filterRegistrationBean.addUrlPatterns("/threadLocal/*");
            return filterRegistrationBean;
        }
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new HttpInteceptor()).addPathPatterns("/**");
        }
    }

    最后编写测试所用Controller

    @RestController
    @RequestMapping("/threadLocal")
    public class ThreadLocalController {
    
        @RequestMapping("/test")
        public Long test() {
            return RequestHolder.getId();
        }
    }

    3 线程不安全类和写法

    一些常见的线程不安全类及其解决方法

    1)StringBuilder(线程不安全,单线程推荐使用,效率高) 和 StringBuffer(线程安全,多线程使用)

    2)SimpleDateFormat 线程不安全,参考博客:https://blog.csdn.net/csdn_ds/article/details/72984646

    使用jodaTime(线程安全)

    3)ArrayList、HashSet、HashMap等集合是 线程不安全的

    4 同步容器

    对集合进行遍历操作是不要做删除操作

    5 并发容器 J.U.C

    上面提到的同步容器,可以一定程度上保证线程安全,但还是会出现线程不安全的情况,所以出现了并发容器

    1)ArrayList ---> J.U.C中的 CopyOnWriteArrayList .该类在更新操作的时候会加锁,然后先复制一份原来的List,再添加元素,然后重新赋值回原来的。 适合于list元素少,读多于写

    2)

    3)

  • 相关阅读:
    Unity Chan 2D Asset
    Android DataBinding库(MVVM设计模式)
    android中MVC,MVP和MVVM三种模式详解析
    使用MVP模式重构代码
    MVP模式是你的救命稻草吗?
    在谈MVP之前,你真的懂MVC吗?
    浅谈 MVP in Android
    MVC,MVP 和 MVVM 的图示
    Android:MVC模式(下)
    Android入门:MVC模式(中)
  • 原文地址:https://www.cnblogs.com/panlei3707/p/11065780.html
Copyright © 2011-2022 走看看