[ベース] Javaの並行処理のローカル変数は、スレッドセーフです

序文

変数(つまり、ローカル変数)の方法は、本大会のデータ(データ競合)ではありません、また、スレッドセーフです。のは、メソッドが実行されるかを見てみましょう、理由を理解するには、ローカル変数のセキュリティを分析し、最終的には同時実行の問題を解決するための技術に起因する特性の一部を共有していないローカル変数の使用をご紹介。

メソッドが実行される方法

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言語は、仮想マシンによって解釈されているが、だけでなく、メソッド呼び出しスタックフレームの使用は解決します。

ローカル変数の格納位置

ローカル変数は、スコープ内でも、この方法でメソッドを定義しています。プロセスの実行が終了した後、ローカル変数も失敗します。だから我々は、ローカル変数の位置は、コールスタックに格納されるべきであると結論付けることができます。実際には、ローカル変数は、コールスタックに格納されます

コールスタックとスレッド

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

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