Java高并发--线程安全策略

主要是学习慕课网实战视频《Java并发编程入门与高并发面试》的笔记

不可变对象

发布不可变对象可保证线程安全。

实现不可变对象有哪些要注意的地方?比如JDK中的String类。

  • 不提供setter方法(包括修改字段、字段引用到的的对象等方法)
  • 将所有字段设置为final、private
  • 将类修饰为final,不允许子类继承、重写方法。可以将构造函数设为private,通过工厂方法创建。
  • 如果类的字段是对可变对象的引用,不允许修改被引用对象。 1)不提供修改可变对象的方法;2)不共享对可变对象的引用。对于外部传入的可变对象,不保存该引用。如要保存可以保存其复制后的副本;对于内部可变对象,不要返回对象本身,而是返回其复制后的副本。

final关键字可以修饰在类、方法、变量:

  • 类:被修饰的类不能被继承
  • 方法:被修饰的方法不能被重写
  • 变量:被修饰的是一个基本类型,其值不能被修改;被修饰的是一个对象引用,这里的“不可变”指不允许其再指向其他对象,但是可以修改对象里面的值。

关于上一条中final修饰对象的引用。以ArrayList为例

final List<Integer> list= new ArrayList<>();
list.add(3);
list.add(4);
list = new ArrayList<>(); // 编译时报错,不能再指向其他对象
list.set(0, 2); // 但是可以修改list里面的值

假如我们就是要求诸如List、Map一类的数据结构也不能修改其中的元素呢?

JDK中Collections的一些静态方法提供了支持,如下,举一个List的例子

final List<Integer> list= new ArrayList<>();
list.add(3);
list.add(4);
List<Integer> unmodifiableList = Collections.unmodifiableList(list);
System.out.println(unmodifiableList);
unmodifiableList.add(5); // 运行时异常
unmodifiableList.set(0, 2); // 运行时异常

只需要将普通的list传给Collections.unmodifiableList()作为入参即可。

其实现原理也很简单,将普通list中的数据拷贝,然后对于所有添加、修改的操作,直接抛出异常即可,这样就保证了list不能修改其中的元素。

线程安全的问题就是出在多个线程同时修改共享变量,不可变对象的策略完全规避了对对象的修改,所以在多线程中使用也不会有任何问题。

线程封闭

  • 堆栈封闭:能使用局部变量的地方就不使用全局变量,多线程下访问同一个方法时,方法中的局部变量都会拷贝一份到线程的栈中,也就是说每一个线程中都有只属于本线程的私有变量,因此局部变量不会被多个线程共享。
  • ThreadLocal:特别好的线程封闭方法,其实现原理如下

对于共享变量,一般采取同步的方式保证线程安全。而ThreadLocal是为每一个线程都提供了一个线程内的局部变量,每个线程只能访问到属于它的副本。

下面是set和get的实现

// set方法
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

// 上面的getMap方法
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

// get方法
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

从源码中可以看出:每一个线程拥有一个ThreadLocalMap,这个map存储了该线程拥有的所有局部变量。

set时先通过Thread.currentThread()获取当前线程,进而获取到当前线程的ThreadLocalMap,然后以ThreadLocal自己为key,要存储的对象为值,存到当前线程的ThreadLocalMap中。

get时也是先获得当前线程的ThreadLocalMap,以ThreadLocal自己为key,取出和该线程的局部变量。

题话外,一个线程内可以设置多个ThreadLocal,这样该线程就拥有了多个局部变量。比如当前线程为t1,在t1内创建了两个ThreadLocal分别是tl1和tl2,那么t1的ThreadLocalMap就有两个键值对。

t1.threadLocals.set(tl1, obj1) // 等价于在t1线程中调用tl1.set(obj1)
t1.threadLocals.set(tl2, obj2) // 等价于在t1线程中调用tl2.set(obj1)

t1.threadLocals.getEntry(tl1) // 等价于在t1线程中调用tl1.get()获得obj1
t1.threadLocals.getEntry(tl2) // 等价于在t1线程中调用tl2.get()获得obj2

以一个角色验证的例子为例,为每一个请求(线程)保存了当前访问人的角色。比如有guest和admin。

public class CurrentUserHolder {
    private static final ThreadLocal<String> holder = new ThreadLocal<>();

    public static void setUserHolder(String user) {
        holder.set(user);
    }

    public static String getUserHolder() {
        return holder.get();
    }
}

在进行某些敏感操作前,需要对当前请求下的角色进行验证。游客是没有访问权限的,只有管理员可以。

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

@Component
public class AuthService {
    public void checkAccess() {
        // 通过ThreadLocal取得当前线程(请求)中的角色
        String user = CurrentUserHolder.getUserHolder();
        if (!"admin".equals(user)) {
            throw new RuntimeException("操作不被允许!");
        }
    }
}

操作前的验证使用AOP过滤

import com.shy.aopdemo.security.AuthService;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class AuthAspect {
    @Autowired
    private AuthService authService;
    @Pointcut("execution(* com.shy.aopdemo.service.ProductService.*(..))")
    public void adminOnly() {}

    @Before("adminOnly()")
    public void checkAccess() {
        authService.checkAccess();
    }
}

测试一下,如果当前请求的角色是guest

import com.shy.aopdemo.security.CurrentUserHolder;
import com.shy.aopdemo.service.ProductService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class AopdemoApplicationTests {
    @Autowired
    private ProductService productService;
    @Test
    public void checkDeleteTest() {
        // 当前请求的角色是guest
        CurrentUserHolder.setUserHolder("guest");
        // AOP,在delete之前会先调用authService.checkAccess();结果验证不通过
        productService.delete(1L);
    }
}

总结

安全共享对象的策略

  • 线程限制:一个被线程限制的对象,由线程独占;只能由它的线程来修改,例如使用线程内的局部变量、ThreadLocal等
  • 共享只读:只读对象在没有额外同步的情况下,可以被多个线程并发访问,但是任何线程都无法修改它。例如,使用不可变对象(final关键字修饰)
  • 线程安全的对象:一个线程按安全的对象或容器,通过内部的同步机制来保证线程安全。比如StringBuffer、ConcurrentHashMap、AtomicInteger等线程安全对象。

Java高并发--线程安全策略的更多相关文章

  1. Java高并发 -- 线程池

    Java高并发 -- 线程池 主要是学习慕课网实战视频<Java并发编程入门与高并发面试>的笔记 在使用线程池后,创建线程变成了从线程池里获得空闲线程,关闭线程变成了将线程归坏给线程池. ...

  2. JAVA高并发线程

    一.JAVA高级并发 1.5JDK之后引入高级并发特性,大多数的特性在java.util.concurrent 包中,是专门用于多线程发编程的,充分利用了现代多处理器和多核心系统的功能以编写大规模并发 ...

  3. [ 高并发]Java高并发编程系列第二篇--线程同步

    高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...

  4. 【实战Java高并发程序设计 7】让线程之间互相帮助--SynchronousQueue的实现

    [实战Java高并发程序设计 1]Java中的指针:Unsafe类 [实战Java高并发程序设计 2]无锁的对象引用:AtomicReference [实战Java高并发程序设计 3]带有时间戳的对象 ...

  5. java高并发系列 - 第6天:线程的基本操作

    新建线程 新建线程很简单.只需要使用new关键字创建一个线程对象,然后调用它的start()启动线程即可. Thread thread1 = new Thread1(); t1.start(); 那么 ...

  6. 跟着阿里p7一起学java高并发 - 第18天:玩转java线程池,这一篇就够了

    java中的线程池,这一篇就够了 java高并发系列第18篇文章. 本文主要内容 什么是线程池 线程池实现原理 线程池中常见的各种队列 自定义线程创建的工厂 常见的饱和策略 自定义饱和策略 线程池中两 ...

  7. java高并发系列 - 第31天:获取线程执行结果,这6种方法你都知道?

    这是java高并发系列第31篇. 环境:jdk1.8. java高并发系列已经学了不少东西了,本篇文章,我们用前面学的知识来实现一个需求: 在一个线程中需要获取其他线程的执行结果,能想到几种方式?各有 ...

  8. java高并发系列 - 第11天:线程中断的几种方式

    java高并发系列第11篇文章. 本文主要探讨一下中断线程的几种方式. 通过一个变量控制线程中断 代码: package com.itsoku.chat05; import java.util.con ...

  9. java高并发系列 - 第10天:线程安全和synchronized关键字

    这是并发系列第10篇文章. 什么是线程安全? 当多个线程去访问同一个类(对象或方法)的时候,该类都能表现出正常的行为(与自己预想的结果一致),那我们就可以所这个类是线程安全的. 看一段代码: pack ...

随机推荐

  1. Python之路【第五篇】函数

    4.1 函数的定义 函数是指将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用其函数名即可 4.2 函数的创建 函数名的命名规则: 1.函数名必须以下划线或字母开头,可以包含任 ...

  2. Reids学习1 -- 初识Redis

    1. Reids和其他类型数据库对比 名称 类型 数据库存储选项 查询类型 附加功能 Redis 使用内存存储的非关系数据库 字符串,列表,集和,散列表,有序集合 每个类型有自己的专属命令,还有批量操 ...

  3. zookeeper集群配置详细教程

      第一步:环境准备 环境 版本 说明 JDK 1.8 zookeeper运行所需 centos 7 操作系统 需要配置好JDK的环境变量 zookeeper-3.4.9.tar.gz 3.4.9 z ...

  4. 使用bat脚本部署hexo到coding和github

    因项目的不同适当的改造吧,本文以hexo为例. 拉取coding.net的代码和github的代码到本地 确保代码能够正常的运行,commit,push 在项目的目录外新建一个push.bat文件 快 ...

  5. Struts2新漏洞S2-046在线实验环境全球首发

    Strust2 又出现漏洞啦?搞事情啊? 据说S2-046漏洞和S2-045漏洞非常相似,都是由报错信息带入了buildErrorMessage方法造成的, 只是这次存在两个触发点哦!危害嘛,你说嘞? ...

  6. Java核心技术卷一基础知识-第5章-继承-读书笔记

    第5章 继承 本章内容: * 类.超类和子类 * Object:所有类的超类 * 泛型数组列表 * 对象包装器和自动装箱 * 参数数量可变的方法 * 枚举类 * 反射 * 继承设计的技巧 利用继承,人 ...

  7. Python面向对象4:类的相关函数与属性

    1 类相关函数- issubclass:检测一个类是否是另一个类的子类- isinstance:检测一个对象是否是一个类的实例- hasattr:检测一个对象是否由成员xxx- getattr: ge ...

  8. 细说MySQL数据库操作

    目录 基本语法: 字符集和校验规则 字符集 校验规则 校验规则的影响 数据库操作相关指令 查询数据库版本 显示数据库语句 显示数据库创建语句 数据库删除语句 查看当前数据库有多少个用户在操作 基本语法 ...

  9. SpringMVC框架六:拦截器

    拦截器的概念就不介绍了: SpringMVC中拦截器的执行规则: 一个小示例展示: 我搞两个拦截器: 第一个: package org.dreamtech.springmvc.interceptor; ...

  10. [CERC2014] Virus synthesis

    设f[i]为形成极长回文串i的最小操作数.答案为min f[i]+n-len[i]. 在不形成偶回文的情况下形成奇回文的最小操作数为该串长度.可以不考虑(但ans赋为len). 正确性基于: 1)奇. ...