java安全编码指南之:线程安全规则

简介: java安全编码指南之:线程安全规则

目录



简介


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


一起来看看吧。


注意线程安全方法的重写


大家都做过方法重写,我们知道方法重写是不会检查方法修饰符的,也就是说,我们可以将一个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

相关文章
|
6天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
37 6
|
15天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
15天前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
40 3
|
16天前
|
监控 Java 开发者
深入理解Java中的线程池实现原理及其性能优化####
本文旨在揭示Java中线程池的核心工作机制,通过剖析其背后的设计思想与实现细节,为读者提供一份详尽的线程池性能优化指南。不同于传统的技术教程,本文将采用一种互动式探索的方式,带领大家从理论到实践,逐步揭开线程池高效管理线程资源的奥秘。无论你是Java并发编程的初学者,还是寻求性能调优技巧的资深开发者,都能在本文中找到有价值的内容。 ####
|
19天前
|
监控 Java 数据库连接
Java线程管理:守护线程与用户线程的区分与应用
在Java多线程编程中,线程可以分为守护线程(Daemon Thread)和用户线程(User Thread)。这两种线程在行为和用途上有着明显的区别,了解它们的差异对于编写高效、稳定的并发程序至关重要。
28 2
|
7月前
|
安全 Java
java保证线程安全关于锁处理的理解
了解Java中确保线程安全的锁机制:1)全局synchronized方法实现单例模式;2)对Vector/Collections.SynchronizedList/CopyOnWriteArrayList的部分操作加锁;3)ConcurrentHashMap的锁分段技术;4)使用读写锁;5)无锁或低冲突策略,如Disruptor队列。
49 2
|
7月前
|
存储 安全 Java
深入理解Java并发编程:线程安全与锁机制
【5月更文挑战第31天】在Java并发编程中,线程安全和锁机制是两个核心概念。本文将深入探讨这两个概念,包括它们的定义、实现方式以及在实际开发中的应用。通过对线程安全和锁机制的深入理解,可以帮助我们更好地解决并发编程中的问题,提高程序的性能和稳定性。
|
5月前
|
存储 SQL 安全
Java共享问题 、synchronized 线程安全分析、Monitor、wait/notify以及锁分类
Java共享问题 、synchronized 线程安全分析、Monitor、wait/notify以及锁分类
49 0
|
7月前
|
安全 Java API
Java 8中的Stream API:简介与实用指南深入理解Java并发编程:线程安全与锁优化
【5月更文挑战第29天】本文旨在介绍Java 8中引入的Stream API,这是一种用于处理集合的新方法。我们将探讨Stream API的基本概念,以及如何使用它来简化集合操作,提高代码的可读性和效率。 【5月更文挑战第29天】 在Java并发编程中,线程安全和性能优化是两个核心议题。本文将深入探讨如何通过不同的锁机制和同步策略来保证多线程环境下的数据一致性,同时避免常见的并发问题如死锁和竞态条件。文章还将介绍现代Java虚拟机(JVM)针对锁的优化技术,包括锁粗化、锁消除以及轻量级锁等概念,并指导开发者如何合理选择和使用这些技术以提升应用的性能。
|
7月前
|
安全 Java
【JAVA进阶篇教学】第十篇:Java中线程安全、锁讲解
【JAVA进阶篇教学】第十篇:Java中线程安全、锁讲解
下一篇
DataWorks