Java 并发编程基础-共享对象

 

Java 并发编程基础 - 共享对象

 

目录

Java 并发编程基础 - 共享对象 ... 1

1.            可见性 ... 1

1.1过期数据 ... 2

1.2 非原子的 64位操作 ... 3

1.3 Volatile变量是有代价的 ... 3

2.            发布和逸出 ... 3

3.            线程封闭 ... 5

3.1      栈限制 ... 5

3.2      ThreadLocal 5

3.3      不可变性 ... 6

 

1. 可见性

在没有同步的情况下,编译器,处理器,运行时安排操作的执行顺序可能完全出人意料,在没有进行适当同步的多线程程序中,尝试推断哪些“必要”发生在内存中的动作是,你总是会判断错误。

Public class NoVisibility{

      Private static boolean ready;

      Private static int number;

      Private static class ReaderThread extends Thread{

           Public void run(){

                 While(!ready)

                      Thread.yield();

                 System.out.println(number);

}

}

 

Public static void main(String[] args){

      New ReaderThread().start();

      Number = 42;

      Ready = true;

}

}

因为没有进行适当的同步, ready 的值为 true ,对读线程来说, ready 的值可能永远不可见,甚至 NoVisibility 可能会打印出 0. 因为早在对 number 赋值之前,主线程就已经写入 ready 并使之对读线程可见,这是一种“重排序 (Reordering) ”现象。

 

详见关于重排序的说明。

1.1 过期数据

当一个线程调用 set ,另外一个线程调用 get ,读线程和可能得到一个过期的数据:

@NotThreadSafe

Public class MutableInteger{

      Private int value;

      Public int get(){     return value;     }

      Public void set(int value){    this.value=value;   }

}

通过 Java 的同步机制,可以保证可见性,保证强的一致性(可以立即看到更新后的结果)

@ThreadSafe

Public class MutableInteger{

      Private int value;

      Public synchronized int get(){       return value;     }

      Public synchronized void set(int value){this.value=value;}

}

 

1.2 非原子的 64 位操作

Java 存储模型要求获取和存储操作都为原子的,但是对于非 volatile long double 变量, JVM 允许将 64 位的读或写划分为两个 32 位操作。如果读和写发生在不同的线程,这种情况读取了一个非 volatile 类型 long 就可能得到一个值的高 32 位和另一个值的低 32 位。

 

 

1.3 Volatile 变量是有代价的

现代处理器的发展,让 CPU 的处理能力得到了质的飞跃,这一切都与 cpu 的高速缓存有关系,这就是为什么 Java 内存模型里面,为什么各自线程有自己的工作内存的原因了,另外也由于直接操作主存,需要耗费更多的 CPU 时钟周期,但是如果存在线程安全问题, volatile 不会使用运行线程所在的 CPU 里面的缓存来读取和写输入, volatile 直接操作的是主存。因此实际上, volatile 为了保全可见性,也牺牲了很多性能。

 

 

2. 发布和逸出

发布 一个对象指的是使它能够被当前范围之外的代码所使用。比如讲一个引用存储到其他代码可以访问的地方,在一个非私有的方法中返回这个引用,也可以把它传递给其它的方法。

 

逸出 指的是一个对象在尚未准备好时就将他发布。

 

不正确的发布对象 - 范围逸出:任何一个调用者都可以修改他的内容,在下例中,数组逸出了它所属的范围。这个本应该是私有的数据,事实上已经变成公有的了。

Public class  UnsafeStates{

      Private String[] states = new String[]{“AK”,”AL”,...;

      Public String[] getStates(){return states;}

}

不正确的发布对象 -This 引用在构造时逸出,当对象并没有完全构造好就被新线程使用。

Public class ThisEscape{

Public ThisEscape(){

      New Thread(){

                 Public void   run(){

                      doSomething();

doSomething1();

                 }

           }).start();

}

 

注意对象只有通过构造函数返回后,才处于可预言的、稳定的状态,所以从构造函数内部发布的对象只是一个未完成构造的对象。甚至即使是在构造函数的最后一行发布的引用也是如此。

 

3. 线程封闭

3.1   栈限制

栈限制是线程限制的一种特例,在栈限制中,只能通过本地变量才可以触及对象,本地变量使对象更容易被限制在线程本地中。本地变量本身就被限制在执行线程中;它们存在于执行线程栈。

3.2   ThreadLocal

ThreadLocal 把变量限制在线程内访问,这样两个线程之间不存在共享访问的问题,两个线程各自维护自己的变量。下面的例子说明了,一个原本可以被多线程法访问的变量,经过 ThreadLocal 的改进,变成了只有当前线程才能访问和修改的变量,这样做自然线程就安全了。

public class ThreadLocalImageServlet extends HttpServlet{

      public ThreadLocal<String> imageURL = new ThreadLocal<String>(){

           public String initialValue(){

                 return "";

           }

      };

      protected void doGet(HttpServletRequest req, HttpServletResponse resp)

                 throws ServletException, IOException {

           imageURL.set(req.getParameter("imageURL"));

           resp.getWriter().println(imageURL.get());

      }

}

3.3   不可变性

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值