您的位置:首页 > 编程语言 > Java开发

Java 并发编程(二)——对象共享

2017-02-05 23:00 441 查看

前言

上一节中主要是说明并发中的原子性概念,主要是通过Synchronize关键字来创建临界区(Critical Section)实现原子性。这片主要讲的是并发的另外一个重要的方面:内存可见性(保证线程修改状态时可以被其他共享使用该对象状态的线程看得到)。

1.可见性

可见性是指在多线程中并发变量,由于读操作和写操作可以在不同的线程中进行,此时,并没法确保能看到其他线程都写入的值。主要原因应该是线程中操作完成应该只是在工作内存中,并没有及时的将数据写入到主内存中,从而仍然有可能存在“脏数据”。

例如一下一个代码中:

public class NoVisibility {
private static boolean ready;
private static int number;

private static class ReaderThread extends Thread {
@Override
public void run() {
while(!ready) {
Thread.yield();
}
System.out.println(number);
}
}

public static void main(String[] args) {
new ReaderThread().start();
number = 42;
ready = true;
}
}


在理论上,代码没有采取任何同步的操作,主线程中更新的number不一定能够被ReadThread线程看到,从而输出0,也有可能会一直循环。为了避免这种情况的发生,就希望能够将共享的对象实时更新到内存中,从而其他共享的线程可以看到这些共享的线程状态。

1.1 使用同步机制

由于同步使用的是内部锁,在锁关闭之后会实时的将数据写入主内存,从而就保证了内存的可见性。

1.2 volatile关键字

volatile变量:这种方式主要是可以对共享变量的读保证互斥,一次只能允许一个线程操作;对于读操作时,并不会执行线程阻塞,可以多线程并发。

重要用法:

检查某个标记状态以判断是否退出循环:

private volatile boolean asleep;
while (!asleep) {
dosomething!
}


2.发布与溢出

2.1 发布

发布是指对象能够在当前作用域之外的代码中使用。主要有三种情况:1、将一个对象引用保存到一个其他代码可以访问得到的地方(比如公有方法),2、在某一个非私有的方法不被发布。3、将引用传递到其他类的方法中。

溢出:在对象的发布过程中,将不安全的对象状态发布出去,这种情况就成为溢出。

2.2 溢出的三种情况:

一种是将一个引用保存在一个公共的静态变量。

private String[] states = new String[] {
"AK", "AL"
};
public String[] getStates() {
return states;
}


如上,如果通过getStates来发布states,这个发布就会导致states溢出其所在的作用域。

第二种,就是发布一个内部类的对象,会隐含的将外部类的引用发布,所以这个也会溢出。

public class NotSafeListener {
public NotSafeListener(EventSource source) {
source.registerListener {
new EventListener() {
public void onEvent(Event e) {
System.out.println("do Something");
}
};
}
}
}


主要的原因就是发布一个内部对象时会导致外部类的引用被发布,从而可能导致难以预计的后果。

可以利用一个私有的构造函数和一个公有的初始化函数来实现这些问题。

public class SafeListener {
private final EventListener listener;

private SafeListener() {
listener = new EventListener() {
public void onEvent(Event e) {
System.out.println("do Something");
}
};
}

public static SafeListener newInstance(EventSource Source) {
SafeListener safeListener = new SafeListener();
source.registerListener(safeListener.listener);
return safeListener;
}
}


安全的对象构造过程

主要的在于在构造过程中不要将this引用溢出。

3.线程封闭

线程封闭是指在并发过程中不使用共享的数据,将相关对象的状态都作为单线程内部的数据,从而避免了并发共享变量所导致的一系列的问题。

主要依靠ThreadLocal来保证整个程序的运行(提供了get和set方法,并为每个变量保存一份独立的副本)。

3.1 主要的使用场景

防止可变的单例变量(singleton)和全局变量进行共享。

private static String DB_URL = "";
private static ThreadLocal<Connection> connectionThreadLocal  =
new ThreadLocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection() {
return connectionThreadLocal.get();
}


参考书籍:Java 并发实战(Java Concurrency in Practice)—- Brian Goetz / Tim Peierls / Joshua Bloch / Joseph Bowbeer / David Holmes / Doug Lea
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  并发 java 多线程