欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  Java

Java 线程全和共享资源

程序员文章站 2022-04-14 17:28:31
...
代码被多线程同时的调用是安全的称之为线程安全。如果一段代码是线程安全的,那么它就不包含竞态条件。竞态条件只是发生在多线程更新共享资源的时候。因此知道Java线程什么时间执行共享的资源是重要的。

局部变量

局部变量存储在每一个线程自己的栈中。那就意味着局部变量在线程之间不会共享。那也意味着所有的局部原始变量是线程安全的。这里有一个例子:


public void someMethod(){

  long threadSafeInt = 0;

  threadSafeInt++;
}


局部对象引用


对于对象的局部引用是有点不同的。这个引用本身是不会共享的。然而,这个对象的引用是不能存储在每一个线程的栈中。所有的对象都存储在共享的堆中。

如果局部创建的对象没有逸出它创建的方法,它是线程安全的。事实上你也可以把他传递给其他的方法,并且只要传递的这个对象的它的方法对于其他的线程是不可用的。

这里有一个例子:


public void someMethod(){

  LocalObject localObject = new LocalObject();

  localObject.callMethod();
  method2(localObject);
}

public void method2(LocalObject localObject){
  localObject.setValue("value");
}


在这个例子中的LocalObject实例不能从这个方法中返回,也不能传递给其他的对象。那个从someMethod方法的外部是可以访问的。每一个执行someMethod方法的线程将会创建它自己的LocalObject实例,以及把它分配给localObject引用。因此这种使用是线程安全的。

事实上,这整个someMethod方法是线程安全的。甚至如果这个localObject实例被作为参数传递给相同类的其他方法,或者其他的类,它是线程安全的使用。

当然,唯一的异常,如果这些方法中的一个使用LocalObject作为参数调用,在某种程度上存储LocalObject实例,允许来自其他线程的访问。

对象成员变量

对象成员变量(字段)连同对象一起存储在堆上。因此,如果两个线程调用相同对象实例的一个方法,以及这个方法更新对象成员变量,整个方法就不是线程安全的。这里有一个例子:


public class NotThreadSafe{
    StringBuilder builder = new StringBuilder();

    public add(String text){
        this.builder.append(text);
    }
}


如果两个线程在相同的NotThreadSafe实例上同时的调用add方法,它就会导致竞态条件。例如:



NotThreadSafe sharedInstance = new NotThreadSafe();

new Thread(new MyRunnable(sharedInstance)).start();
new Thread(new MyRunnable(sharedInstance)).start();

public class MyRunnable implements Runnable{
  NotThreadSafe instance = null;

  public MyRunnable(NotThreadSafe instance){
    this.instance = instance;
  }

  public void run(){
    this.instance.add("some text");
  }
}


注意这两个MyRunnable实例是怎么样分享相同的NotThreadSafe实例的。因此,当他们调用add方法的时候会导致竞态条件。


然而,如果两个线程在不同的实例上同时的调用add方法,它就不会导致竞态条件。这里是来自于之前的例子,稍微有些修改:


new Thread(new MyRunnable(new NotThreadSafe())).start();
new Thread(new MyRunnable(new NotThreadSafe())).start();

现在每一个线程都有一个他们自己的NotThreadSafe实例了,以至于他们调用这个add方法不会互相干扰。这个代码不带有竞态条件。以至于,甚至一个对象不是线程安全的,它仍然可以用这种方式使用不会导致竞态条件。

线程控制溢出规则

当尝试决定是否你的代码对某个资源访问是线程安全的时候,你可以使用下列规则:


If a resource is created, used and disposed within
the control of the same thread,
and never escapes the control of this thread,
the use of that resource is thread safe.


资源可以是任何共享的资源,像一个对象,数组,文件,数据库连接,套接字等等。在Java中,你不能总是明确的销毁对象,以至于“销毁的”意味着对象丢失或者空的引用。


甚至如果一个对象的使用是线程安全的,如果那个对象指向了一个共享的资源像一个文件或者数据库,你的应用作为一个整体可能就不是线程安全的了。例如,如果线程1和线程2各自创建他们自己的数据库连接,连接1和连接2,每一个他们自己的连接使用是线程安全的。但是这个连接指向的数据库的使用可能就不是线程安全的。例如,如果两个线程执行像这样的代码:


check if record X exists
if not, insert record X


如果两个线程同时执行这个,以及他们正在检查的这个记录X放生在相同的记录上,这里就有一个风险,他们都会最终已插入而结束。如下所示:

Thread 1 checks if record X exists. Result = no
Thread 2 checks if record X exists. Result = no
Thread 1 inserts record X
Thread 2 inserts record X


这个也会发生在操作在文件或者其他的共享资源的线程上。因此去区分被一个线程控制的对象是否是一个资源,或者仅仅是这个资源(像数据库连接)的引用是重要的。

以上就是Java 线程全和共享资源的内容,更多相关内容请关注PHP中文网(www.php.cn)!