简介

如果我们在多线程中引入了共享变量,那么我们就需要考虑一下多线程下线程安全的问题了。那么我们在编写代码的过程中,需要注意哪些线程安全的问题呢?

一起来看看吧。

注意线程安全方法的重写

大家都做过方法重写,我们知道方法重写是不会检查方法修饰符的,也就是说,我们可以将一个synchronized的方法重写成为非线程安全的方法:

public class SafeA {
public synchronized void doSomething(){
}
}
public class UnsafeB extends SafeA{
@Override
public void doSomething(){
}
}

我们在实现子类功能的时候,一定要保持方法的线程安全性。

构造函数中this的溢出

this是什么呢?根据JLS的规范,当用作主要表达式时,关键字this表示一个值,该值是对其调用实例方法的对象或正在构造的对象的引用。

那么问题来了,因为this能够表示正在构造的对象,那么意味着,如果对象还没有构建完毕,而this又可以被外部访问的话,就会造成外部对象访问到还未构造成功对象的问题。

我们来具体看一下this溢出都会发生在哪些情况:

public class ChildUnsafe1 {

    public static ChildUnsafe1 childUnsafe1;
int age; ChildUnsafe1(int age){
childUnsafe1 = this;
this.age = age;
}
}

上面是一个非常简单的this溢出的情况,在构造函数的过程中,将this赋值给了一个public对象,将会导致this还没有被初始化完毕就被其他对象访问。

那么我们调整一下顺序是不是就可以了呢?

public class ChildUnsafe2 {

    public static ChildUnsafe2 childUnsafe2;
int age; ChildUnsafe2(int age){
this.age = age;
childUnsafe2 = this;
}
}

上面我们看到,this的赋值被放到了构造方法的最后面,是不是就可以避免访问到未初始化完毕的对象呢?

答案是否定的,因为java会对代码进行重排序,所以childUnsafe2 = this的位置是不定的。

我们需要这样修改:

public class Childsafe2 {

    public volatile static Childsafe2 childUnsafe2;
int age; Childsafe2(int age){
this.age = age;
childUnsafe2 = this;
}
}

加一个volatile描述符,禁止重排序,完美解决。

我们再来看一个父子类的问题,还是上面的Childsafe2,我们再为它写一个子类:

public class ChildUnsafe3 extends Childsafe2{

    private Object obj;

    ChildUnsafe3(int age){
super(10);
obj= new Object();
} public void doSomething(){
System.out.println(obj.toString());
}
}

上面的例子有什么问题呢?因为父类在调用构造函数的时候,已经暴露了this变量,所以可能会导致ChildUnsafe3中的obj还没有被初始化的时候,外部程序就调用了doSomething(),这个时候obj还没有被初始化,所以会抛出NullPointerException。

解决办法就是不要在构造函数中设置this,我们可以新创建一个方法,在构造函数调用完毕之后,再进行设置。

不要在类初始化的时候使用后台线程

如果在类初始化的过程中,使用后台进程,有可能会造成死锁,我们考虑下面的情况:

public final class ChildFactory {
private static int age; static {
Thread ageInitializerThread = new Thread(()->{
System.out.println("in thread running");
age=10;
}); ageInitializerThread.start();
try {
ageInitializerThread.join();
} catch (InterruptedException ie) {
throw new AssertionError(ie);
}
} public static int getAge() {
if (age == 0) {
throw new IllegalStateException("Error initializing age");
}
return age;
} public static void main(String[] args) {
int age = getAge();
}
}

上面的类使用了一个static的block,在这个block中,我们启动一个后台进程来设置age这个字段。

为了保证可见性,static变量必须在其他线程运行之前初始化完毕,所以ageInitializerThread需要等待main线程的static变量执行完毕之后才能运行,但是我们又调用了ageInitializerThread.join()方法,主线程又需要反过来等待ageInitializerThread的执行完毕。

最终导致了循环等待,造成了死锁。

最简单的解决办法就是不使用后台进程,直接在static block中设置:

public final class ChildFactory2 {
private static int age; static {
System.out.println("in thread running");
age=10;
} public static int getAge() {
if (age == 0) {
throw new IllegalStateException("Error initializing age");
}
return age;
} public static void main(String[] args) {
int age = getAge();
}
}

还有一种办法就是使用ThreadLocal将初始化变量保存在线程本地。

public final class ChildFactory3 {

    private static final ThreadLocal<Integer> ageHolder = ThreadLocal.withInitial(() -> 10);

    public static int getAge() {
int localAge = ageHolder.get();
if (localAge == 0) {
throw new IllegalStateException("Error initializing age");
}
return localAge;
} public static void main(String[] args) {
int age = getAge();
}
}

本文的代码:

learn-java-base-9-to-20/tree/master/security

本文已收录于 http://www.flydean.com/java-security-code-line-threadsafe/

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

java安全编码指南之:线程安全规则的更多相关文章

  1. java安全编码指南之:基础篇

    目录 简介 java平台本身的安全性 安全第一,不要写聪明的代码 在代码设计之初就考虑安全性 避免重复的代码 限制权限 构建可信边界 封装 写文档 简介 作为一个程序员,只是写出好用的代码是不够的,我 ...

  2. java安全编码指南之:Thread API调用规则

    目录 简介 start一个Thread 不要使用ThreadGroup 不要使用stop()方法 wait 和 await 需要放在循环中调用 简介 java中多线程的开发中少不了使用Thread,我 ...

  3. java安全编码指南之:可见性和原子性

    目录 简介 不可变对象的可见性 保证共享变量的复合操作的原子性 保证多个Atomic原子类操作的原子性 保证方法调用链的原子性 读写64bits的值 简介 java类中会定义很多变量,有类变量也有实例 ...

  4. java安全编码指南之:异常处理

    目录 简介 异常简介 不要忽略checked exceptions 不要在异常中暴露敏感信息 在处理捕获的异常时,需要恢复对象的初始状态 不要手动完成finally block 不要捕获NullPoi ...

  5. java安全编码指南之:lock和同步的正确使用

    目录 简介 使用private final object来作为lock对象 不要synchronize可被重用的对象 不要sync Object.getClass() 不要sync高级并发对象 不要使 ...

  6. java安全编码指南之:Mutability可变性

    目录 简介 可变对象和不可变对象 创建mutable对象的拷贝 为mutable类创建copy方法 不要相信equals 不要直接暴露可修改的属性 public static fields应该被置位f ...

  7. java安全编码指南之:声明和初始化

    目录 简介 初始化顺序 循环初始化 不要使用java标准库中的类名作为自己的类名 不要在增强的for语句中修改变量值 简介 在java对象和字段的初始化过程中会遇到哪些安全性问题呢?一起来看看吧. 初 ...

  8. java安全编码指南之:字符串和编码

    目录 简介 使用变长编码的不完全字符来创建字符串 char不能表示所有的Unicode 注意Locale的使用 文件读写中的编码格式 不要将非字符数据编码为字符串 简介 字符串是我们日常编码过程中使用 ...

  9. java安全编码指南之:输入校验

    目录 简介 在字符串标准化之后进行校验 注意不可信字符串的格式化 小心使用Runtime.exec() 正则表达式的匹配 简介 为了保证java程序的安全,任何外部用户的输入我们都认为是可能有恶意攻击 ...

随机推荐

  1. git修改未push和已经push的注释信息

    修改还未push的注释: git commit --amend 修改后保存退出. 刚刚push到远端还没有人其他人下载或改动的: git commit --amend1进入修改页面修改注释信息,修改后 ...

  2. HTML自学第一篇

    教程来自W3CSchool 因为笔者有过开发经验 本篇只是个人对HTML自学的笔记,可能不适合用于给他人理解和学习 什么是 HTML HTML 指的是超文本标记语言 (Hyper Text Marku ...

  3. Java基础一篇过(四)List这篇就够了

    文章更新时间:2020/08/03 一.List介绍 list是Java的一个接口,继承了Collection,常用到的有3个子类实现: ArrayList 底层数据结构是数组.线程不安全 Linke ...

  4. GIT学习与GIEE(码云体验)

    GIT 是一个开源的分布式版本控制系统,可以有效.高速的处理从很小到非常大的项目版本管理.Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件 ...

  5. SpringBoot+RabbitMQ 方式收发消息

    本篇会和SpringBoot做整合,采用自动配置的方式进行开发,我们只需要声明RabbitMQ地址就可以了,关于各种创建连接关闭连接的事都由Spring帮我们了~ 交给Spring帮我们管理连接可以让 ...

  6. Java基础——缓存

    1.缓存 将程序或系统中常用的数据对象存储在像内存这样特定的介质中,以避免在每次程序调用时,重新创建或组织数据所带来的性能损耗,从而提高了系统的整体运行速度 以目前的系统架构来说,用户的请求一般会先经 ...

  7. mysql-11-DML

    #DML语言 /* 数据操作语言 插入:insert 修改:update 删除:delete */ #一.插入语句 /* 语法: insert into 表名(列名...) values(新值...) ...

  8. 创建自定义视图在Android矩阵效果画布教程

    介绍 下面是一个快速教程,教你如何在Android中创建自定义视图.自定义视图创建一个矩阵雨效果. 本教程发布在http://www.androidlearner.com/. 背景 下面是关于如何工作 ...

  9. ng2 父子组件传值 - 状态管理

    一. 父子组件之间进行直接通话 //父组件html <ul> <app-li [value] = "value" (liClick) = "liClic ...

  10. 虚拟主机和ECS的选择——有的坑你可以不躺,有的钱你可以不花(一)

    一直想做网站,由于最开始虚拟主机有优惠,所以三年前买了虚拟主机,后来一直续费,间歇性使用过,发现很多功能都不行​. 昨天准备买新的,然后想起学生购买有优惠,于是开始了学生认证之旅​. 首先,看一下之前 ...