从零开始学多线程之共享对象(二)

 想要使用多线程编程,有一个很重要的前提,那就是必须保证操纵的是线程安全的类.

那么如何构建线程安全的类呢? 1. 使用同步来避免多个线程在同一时间访问同一数据. 2. 正确的共享和安全的发布对象,使多个线程能够安全的访问它们.

那么如何正确的共享和安全的发布对象呢? 这正是这篇博客要告诉你的.

1. 多线程之间的可见性问题.

为什么在多线程条件下需要正确的共享和安全的发布对象呢?

这要说到可见性的问题:

在多线程环境下,不能保证一个线程修改完共享对象的数据,对另一个线程是可见的.

一个线程读到的数据也许是一个过期数据,这会导致严重且混乱的问题,比如意外的异常,脏的数据结构,错误的计算和无限的循环.

举个例子:

    private static class RenderThread extends Thread{         @Override         public void run(){             while(!ready){                 Thread.yield();             }             System.out.println("num = " + num);         }      }          public static void main(String [] args) throws InterruptedException {             new RenderThread().start();             num = 42;             ready = true;           }   }

new RenderThread().start()表示创建一个新线程,并执行线程内的run()方法 ,如果ready的值是false,执行Thread.yield()方法(当前线程休息一会让其他线程执行),这时候再交给main方法的主线程执行,给num赋值42,ready赋值true,然后在任务线程中输出num的值.因为可见性的问题,任务线程可能没有看到主线程对num赋值,而输出0.

我们接下来来看看发布对象也会引发的可见性问题.

2. 什么是发布一个对象

发布: 让对象内被当前范围之外的代码所使用.

public class Publish {     public int num1;      private int num2;      public int getNum2(){         return this.num2;     } }

无论是 publish.num1 还是 publish.getNum2()哪种方法,只要能在类以外的地方获取到对象,我们就称对象被发布了.

如果一个对象在没有完成构造的情况下就发布了,这种情况叫逸出.逸出会导致其他线程看到过期值,危害线程安全.

常见的逸出的情况:

1.最常见的逸出就是将对象的引用放到公共静态域(public static Object obj),发布对象的引用,而在局部方法中实例化这个对象.

public class Test {     public static Set<Object> set;      public void initialize(){         set = new HashSet<>();     } }

2.发布对象的状态,而且状态是可变的(没用final修饰),或状态里包含其他的可变数据.

public class UnsafeStates {     private String [] states = new String[]{"a","b","c"};      public String[] getStates(){         return states;     } }

3.在构造方法中使用内部类. 内部类的实例包含了对封装实隐含的引用.

public class UnsafeStates {      private Runnable r;      public UnsafeStates() {         r = new Runnable() {             @Override             public void run() {                  // 内部类在对象没有构造好的情况下,已经可以this引用,逸出了                 // do something;             }         };     } }

逸出主要会导致两个方面的问题:

  1. 发布线程以外的任何线程都能看到对象的域的过期值,因而看到的是一个null引用或者旧值,即使此刻对象已经被赋予了新值.
  2. 线程看到对象的引用是最新的,但是对象的状态却是过期的.

我们已经了解了逸出的问题,那么如何安全的发布一个对象呢?
为了安全地发布对象,对象的引用以及对象的状态必须同时对其他线程可见(也就是说安全发布就是保证对象的可见性),一个正确创建的对象可以通过下列条件安全发布:

  1. 通过静态初始化器初始化对象的引用.

                        
关键字:
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信