[Base] Java Concurrency local variables are thread-safe

Foreword

The method of variables (ie local variables) are not present competition data (Data Race), is also thread-safe. To understand why, let's take a look at how the method is to be executed, then analyze the security of local variables, and finally introduce the use of local variables do not share some of the characteristics resulting from technology to solve concurrency issues.

How the method is executed

int a = 7;
int[] b = fibonacci(a);
int[] c = b;

The above code into CPU instruction execution, during a method as follows :( FIG schematic from a reference [1])

When calling fibonacci(a)time, CPU first find a way to fibonacci()address (the stack register in the CPU), and then jumps to this address to execute code (blue), and finally the CPU completes the process then returns the next statement of the original method call ( red line).

CPU find a parameter called the method and return address through the stack register. CPU supports a linear structure, because the call and related methods, it is also known as the call stack .

As another example, there are three methods A, B, C. A method called Method B, Method C. Method B calls We will build a following call stack. Each method has its own call stack in the independent space, called a stack frame . Each frame has a corresponding parameter stack and the return address of the method needs. When calling a new method, a new stack frame is created, and pressed into the call stack; When the method returns, the corresponding stack frame is automatically ejected. That is, the stack frame and methods with the total death.

A method of generating three call stack shown above in FIG.

Although the definition of different programming languages, although all have different methods, but their method of implementation of the principle is the same: rely stack structure resolved. Although the Java language is interpreted by a virtual machine, but also the use of the method call stack frame to solve.

Local variables storage position

Local variables are defined in the method, the method also inside the scope. After the end of the process run, local variables also fails. So we can conclude that the position of local variables should be stored in the call stack. In fact, local variables is stored in the call stack .

Call stack and thread

两个线程可以同时用不同的参数调用相同的方法,那么调用栈和线程之间是什么关系呢?答案就是:每个线程都有自己独立的调用栈

所以,Java方法里面的局部变量是不存在并发问题的。每个线程都有自己独立的调用栈,局部变量保存在各自的调用栈中,不会被共享,自然也就没有并发问题。

利用不共享解决并发问题的技术: 线程封闭

当多线程访问没有同步的可变共享变量时就会出现并发问题,而解决方案之一便是使变量不共享。变量不会和其他变量共享,也就不会存在并发问题。仅在单线程里访问数据,不需要同步,我们称之为线程封闭。当某个对象封闭在一个线程中时,这种用法将自动实现线程安全性,即使被封闭的对象本身不是线程安全的。

采用线程封闭技术的案例非常多。例如一种常见的应用便为JDBC的Connection对象。从数据库连接池中获取一个Connection对象,在JDBC规范中并没有要求这个Connection一定是线程安全的。数据库连接池通过线程封闭技术,保证一个Connection对象一旦被一个线程获取之后,在这个Connection对象返回之前,连接池不会将它分配给其他线程,从而保证了Connection对象不会有并发问题。

线程封闭技术的一个具体实现是我们上面提到的局部变量的使用(栈封闭),还有一种需要提一下,即ThreadLocal类。

ThreadLoacl类

维持线程封闭性一种更规范方法是使用ThreadLocal,这个类能使线程中的某个值与保存值的对象相关联起来。ThreadLocal提供了get()set()等访问接口,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get()总是返回由当前执行线程在调用set()时设置的最新值。

ThreadLocal对象通常用于防止对可变的单实例变量(Singleton)或全局变量进行共享
例如,在单线程应用程序中可能会维持一个全局的数据库连接,并在线程启动时初始化这个连接对象,从而避免在调用每个方法时都要传递一个Connection对象。由于JDBC的连接对象不一定线程安全的,因此,当多线程应用程序在没有协同的情况下使用全局变量时,就不是线程安全的。通过将JDBC的连接保存到ThreadLocal对象中,每个线程都会拥有属于自己的连接。

如以下代码所示,利用ThreadLocal来维持线程的封闭性:(代码来自参考[2])

public class ConnectionDispenser {
    static String DB_URL = "jdbc:mysql://localhost/mydatabase";

    private ThreadLocal<Connection> connectionHolder
        = new ThreadLocal<Connection>() {
        public Connection initialValue() {
            try {
                return DriverManager.getConnection(DB_URL);
            } catch (SQLException e) {
                throw new RuntimeException("Unable to acquire Connection, e");
            }
        };
    };

    public Connection getConnection() {
        return connectionHolder.get();
    }
}

当某个频繁执行的操作需要一个临时对象,例如一个缓冲区,而同时又希望避免在每次执行时都重新分配该临时对象,就可以使用这项技术。例如,在Java 5.0之前,Integer.toString()方法使用ThreadLocal对象来保存一个12字节大小的缓冲区,用于对结果进行格式化,而不是使用共享的静态缓冲区(需要使用加锁机制)或者每次调用时都分配一个新的缓冲区。

小结

知道方法是如何调用的也就明白了局部变量为什么是线程安全的。方法调用会产生栈帧,局部变量会放在栈帧的工作内存中,线程之间不共享,故不存在线程安全问题。后面我们介绍了基于不共享解决并发问题的线程封闭技术,除了不共享这种思想可以解决并发问题,还有两种:使用不可变变量和正确使用同步机制。

参考:
[1]极客时间专栏王宝令《Java并发编程实战》
[2]Brian Goetz.Tim Peierls. et al.Java并发编程实战[M].北京:机械工业出版社,2016

Guess you like

Origin www.cnblogs.com/myworld7/p/12264504.html