并发编程的第二部分,先来谈谈发布(Publish)逸出(Escape);

发布是指:对象能够在当前作用域之外的代码中使用,例如:将对象的引用传递到其他类的方法中,对象的引用保存在其他类可以访问的地方,或在某个非私有的方法中返回对象的引用;

逸出是指:发布内部状态可能会破坏封装性,如果在对象构造完成之前就发布该对象,就会破坏线程安全性;

下面结合一个例子来理解:

class UnsafeStates {
private String[] states = new String[] {
"AA" , "BB", .....
};
public String[] getStates() { return states; }
}

通过返回对象引用来发布states数组,任何调用者都能修改这个数组的内容,故此数组对象逸出了,有可能引起线程安全性问题;

隐式this引用逸出:

public class ThisEscape {
public ThisEscape (EventSource source) {
source.registerListrner(new EventListner() {
public void onEvent() {
doing(e);
});
}
         //一些初始化操作
}
    public void doing(){
      //doing
    }  
}

在构造ThisEscape对象时,匿名内部类被发布,内部的this.doing()的this也被发布,但构造过程并没结束,在线程执行到内部类中,发布了ThisEscape对象之后,此时ThisEscape对象已经对于其它线程可见了,但还有一些初始化操作没做,故产生了逸出;

避免逸出的最好的方法是:私有化构造器,通过静态方法(工厂方法)返回构造完毕的对象;

public class SafeListner {
privat final EventListner listner; private SafeListner() {
listner = new EventListner(){
public void onEvent(Event e) {
doing(e):
}
}
} public static SafeListner newInstance(){
SafeListner safe = new SafeListner();
source.registerListner(safe.listner);
return safe;
}
}

接下来了解一下线程封闭(Thread Confinement)

实现线程安全性的最简单的方式之一就是线程封闭,也就是单线程内访问数据,就不需要同步;

例如,JDBC中的Connection对象并不要求时线程安全的,线程从连接池中获取一个Connection对象,并用该对象处理请求,使用完返还给线程池;

1.栈封闭:利用局部变量实现,例如,封闭参数引用,不返回引用类型数据等工作;

public int loadTheArk(Collection<Animal> candidates) {
SortedSet<Animal> animals;
int numPairs = 0;
Animal candidate = null; //animal被封闭,不要发布到外界!
animals = new TreeSet<Animal>(new SpeciesGenderComparator());
animals.addAll(candidates);
for(Animal a : animals){
//...
}
return numPairs;
}

新建一个局部集合animals,用于封闭方法接收的参数引用,保证它不会逸出,返回值必须是基本类型(只返回值),而不是引用类型(返回引用,对象逸出);

2.ThreadLocal类:能使线程中的某个值与保存值的对象相关联,提供get与set访问接口与方法,为每个使用该变量的线程都存有一份独立的副本,通常用于防止对可变的单实例变量或全局变量进行分享;

  例如:JDBC的连接对象不一定使线程安全的,因此,将连接保存在ThreadLocal对象中,没给线程会拥有自己的连接

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

每次调用get方法时都会调用initialValue()来获取连接对象;ThreadLocal变量类似于全局变量,它能降低代码的可重用性,并在类之间引入隐含的耦合性.---Java并发编程实战

再来谈谈不变性:

不可变对象一定是线程安全的;

需要满足以下条件:

  1.对象创建后不能修改;

  2.所有域都是final;

  3.对象使正确创建的(this没有逸出).

例如:

public final class ThreeStooges {
private final Set<String> stooges = new HashSet<String>(); public ThreeStooges(){
stooges.add("Joey");
stooges.add("Moe");
stooges.add("Curly");
} public boolean isStooge(String name) {
return stooges.contains(name);
}
}

以上代码中,Set对象构造完成后无法对其进行修改,所有域为final,也没有this逸出,因此ThreeStooges是不可变对象;

结合Volatile和不可变对象来实现线程安全:

不可变对象

class OneValueCache {
private final BigInteger lastNumber;
private final BigInteger[] lastFactors; public OneValueCache(BigInteger i, BigInteger[] factors) {
lasrNumber = i;
lastFactors = Arrays.copyOf(factors, factors.length);
} public BigInteger[] getFactors(BigInteger i) {
if(lastNumber == null || !lastNumber.equals(i))
return null;
else
return Arrays.copyOf(lastFactors, lastFactors.length);
}
}

用Volatile发布不可变对象:

public class VolatileCachedFactorizer implements Servlet {
private volatile OneValueCache cache = new OneValueCache(null, null); public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = cache.getFactors(i);
if(factors == null){
factors = factor(i);
cache = new OneValueCache(i, factors);
}
recodeIntoResponse(resp, factors);
}
}

volatile提供了线程可见性,当设置引用时,其它线程能够看到新缓存的数据,且与cache相关的操作不会相互干扰,因为OneValueCache是一个不可变对象,使得没有使用锁的情况下也能够拥有线程安全性和可见性;

之前工厂模式解决的是如何让对象不被发布出去,现在我们谈谈如何正确发布对象:

先来看看不正确的发布是怎样的:

public class Holder {
private int n;
public Holder(){ this.n = n; } public void asertSanity() {
if(n != n) {
throw new AssertExceptionError("This statement is false!");
}
}
}

由于没有确保Holder对象的状态被其它线程可见,会有线程安全的问题,例如:一个线程A,一个线程B,首先Holder被初始化,n默认值为0,赋值后为1,但是因为没有保证可见性,1这个时候还在当前线程的缓存中,并没有更新到主存中去,所以A可能看到的是缓存中的值1,但是B线程的缓存和A是独立的,所以他看到的还是主存中的值0,这就发现了线程不安全的问题,A看到的是最新值,但B看到的是过期值。因此这是线程不安全的.

如何正确发布呢?

  1.在静态初始化函数中初始化一个对象的引用;

  2.将对象的引用保存到volatile类型的域或者AtomicReferance对象中;

  3.将对象的引用保存到某个正确构造对象的final类型域中;

  4.将对象的引用保存到一个由锁保护的域中;

并发程序中使用或者共享对象时,实用策略小结:

  1.线程封闭:临界资源只被一个线程拥有,对象被封闭在该线程中,并只有本线程才有修改权;

  2.只读共享:共享的对象可以由多个线程并发访问,但任何线程都不能修改它,共享的只读对象包括不可变对象和事实不可变对象;

  3.线程安全共享:线程安全对象在内部实现同步,因此多个线程可以通过对象的公有接口来进行访问而不需要进一步的同步;

  4.保护对象:只能通过锁来访问对象,包括封装在其它线程安全对象中的对象,以及已发布的并且某个特定锁保护的对象.

Java并发(二):基础概念的更多相关文章

  1. java并发编程基础概念

    本次内容主要讲进程和线程.CPU核心数和线程数.CPU时间片轮转机制.上下文切换,并行和并发的基本概念以及并发编程的好处和注意事项,为java并发编程打下扎实基础. 1.什么是进程和线程 1.1 进程 ...

  2. Java并发(一):基础概念

    对于Java并发,我也是属初学阶段,用的参考书是:"Java并发编程实战",写博时也参考了很多类似主题的博客,博主意在记录自己的学习路程,供网友讨论学习之用; 周末写的差不多了,今 ...

  3. Java并发编程--基础进阶高级(完结)

    Java并发编程--基础进阶高级完整笔记. 这都不知道是第几次刷狂神的JUC并发编程了,从第一次的迷茫到现在比较清晰,算是个大进步了,之前JUC笔记不见了,重新做一套笔记. 参考链接:https:// ...

  4. Java并发编程基础

    Java并发编程基础 1. 并发 1.1. 什么是并发? 并发是一种能并行运行多个程序或并行运行一个程序中多个部分的能力.如果程序中一个耗时的任务能以异步或并行的方式运行,那么整个程序的吞吐量和可交互 ...

  5. 并发-Java并发编程基础

    Java并发编程基础 并发 在计算机科学中,并发是指将一个程序,算法划分为若干个逻辑组成部分,这些部分可以以任何顺序进行执行,但与最终顺序执行的结果一致.并发可以在多核操作系统上显著的提高程序运行速度 ...

  6. Java并发(基础知识)—— Executor框架及线程池

    在Java并发(基础知识)—— 创建.运行以及停止一个线程中讲解了两种创建线程的方式:直接继承Thread类以及实现Runnable接口并赋给Thread,这两种创建线程的方式在线程比较少的时候是没有 ...

  7. java并发编程基础——线程的创建

    一.基础概念 1.进程和线程 进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程.(进程是资源分配的最小单位) 线程:同一类线程共享代码和数据 ...

  8. Java并发编程基础-线程安全问题及JMM(volatile)

    什么情况下应该使用多线程 : 线程出现的目的是什么?解决进程中多任务的实时性问题?其实简单来说,也就是解决“阻塞”的问题,阻塞的意思就是程序运行到某个函数或过程后等待某些事件发生而暂时停止 CPU 占 ...

  9. Java并发编程基础三板斧之Semaphore

    引言 最近可以进行个税申报了,还没有申报的同学可以赶紧去试试哦.不过我反正是从上午到下午一直都没有成功的进行申报,一进行申报 就返回"当前访问人数过多,请稍后再试".为什么有些人就 ...

随机推荐

  1. 洛谷P3252 [JLOI2012]树

    题目描述 在这个问题中,给定一个值S和一棵树.在树的每个节点有一个正整数,问有多少条路径的节点总和达到S.路径中节点的深度必须是升序的.假设节点1是根节点,根的深度是0,它的儿子节点的深度为1.路径不 ...

  2. [转]Mac技巧——让Mac轻松访问Windows网络共享

    Mac技巧——让Mac轻松访问Windows网络共享   用Mac(MacBook Pro)有段时间了,用一个字概括,那就是“爽”!当然,也有不爽的时候,比如说键盘键位变了,用eclipse的快捷键让 ...

  3. BZOJ2877:[NOI2012]魔幻棋盘

    浅谈树状数组与主席树:https://www.cnblogs.com/AKMer/p/9946944.html 题目传送门:https://lydsy.com/JudgeOnline/problem. ...

  4. CF 504 E —— Misha and LCP on Tree —— 树剖+后缀数组

    题目:http://codeforces.com/contest/504/problem/E 快速查询LCP,可以用后缀数组,但树上的字符串不是一个序列: 所以考虑转化成序列—— dfs 序! 普通的 ...

  5. AI-Info-Micron-Insight:Micron 美光的技术帮助 CERN 解开宇宙奥秘

    ylbtech-AI-Info-Micron-Insight:Micron 美光的技术帮助 CERN 解开宇宙奥秘 1.返回顶部 1. Micron 美光的技术帮助 CERN 解开宇宙奥秘 大约 14 ...

  6. HDU 3549 Flow Problem (最大流ISAP)

    Flow Problem Time Limit: 5000/5000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others)Tota ...

  7. django根据不同app配置相应的log文件

    django根据不同app配置相应的log文件 settings.py # django logging LOG_PATH = "/var/log/blog/" LOGGING = ...

  8. 最优化理论-Simplex线性规划

     Sorry,各位,现在这里面啥也没,之所以开这篇文章,是防止以后用得到:现在研究这些,总感觉有些不合适,本人还不到那个层次:如果之后有机会继续研究simplex-线性规划问题,再回来参考下面的链接进 ...

  9. 手机端处理布局rem

    方法一 if (document.documentElement.clientWidth > 600) {//页面宽度大于600px让其宽度等于600px,字体大小等于60px,居中 docum ...

  10. AI决策算法 之 GOAP (一)

    http://blog.csdn.net/lovethrain/article/details/67632033 本系列文章内容部分参考自:http://gamerboom.com/archives/ ...