[자료] 자바 동시성 지역 변수는 스레드 안전

머리말

변수의 방법 (즉, 로컬 변수)도 스레드 안전 본 대회 데이터 (데이터 레이스) 아니다. 의이 방법을 실행하는 방법을 살펴 보자 이유를 이해하려면 다음 지역 변수의 보안을 분석하고, 마지막으로 동시성 문제를 해결하기 위해 기술에 의한 특성의 일부를 공유하지 않는 지역 변수의 사용을 소개합니다.

메소드가 실행되는 방법

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

방법 중 CPU 명령 실행으로 상기 코드는 다음과 같은 기준에서 :(도 회로도 [1])

호출 할 때 fibonacci(a)시간, CPU 먼저 할 수있는 방법 찾기 fibonacci()(주소 (는 CPU의 스택 레지스터), 다음 코드 (파란색)을 실행하기 위해이 주소로 점프, 그리고 마지막으로 CPU는 프로세스가 다음 원래 메서드 호출의 다음 문을 반환 완료 레드 라인).

CPU는 스택 레지스터를 통해 방법 및 반환 주소라는 매개 변수를 찾을 수 있습니다. 전화 및 관련 방법은, 그것이라고도 때문에 CPU는 선형 구조를 지원하는 호출 스택 .

다른 예로서, 세 가지 방법 A, B, C. 거기 방법 B라는 방법, 방법 C. 방법 B 통화 우리는 다음과 같은 호출 스택을 구축 할 것입니다. 각 방법은라는 독립적 인 공간에서 자신의 호출 스택,이 스택 프레임을 . 각 프레임은 대응하는 파라미터 스택있어서 요구 리턴 어드레스를 갖는다. 새로운 방법을 호출 할 때, 새로운 스택 프레임을 생성하고 호출 스택에 압입되어있어서 되돌아가, 해당 스택 프레임이 자동적으로 배출 될 때. 이며, 상기 스택 프레임 전체 죽음 방법.

도에 보이는 발생 세 호출 스택의 제조 방법.

모두가 다른 방법을 가지고 있지만 원칙의 구현의 자신의 방법이 동일하지만 서로 다른 프로그래밍 언어의 정의는, 비록 : 의존 스택 구조가 해결. 자바 언어는 가상 머신에 의해 해석되어 있지만, 또한 메서드 호출 스택 프레임의 사용은 해결합니다.

로컬 변수 저장 위치

로컬 변수는 범위 안에 또한, 상기 방법에있어서 정의된다. 프로세스 실행의 종료 후, 지역 변수도 실패합니다. 그래서 우리는 지역 변수의 위치가 호출 스택에 저장되어야한다는 결론을 내릴 수있다. 실제로, 로컬 변수는 호출 스택에 저장된다 .

콜 스택과 스레드

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

所以,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

추천

출처www.cnblogs.com/myworld7/p/12264504.html