并发编程的第二部分,先来谈谈发布(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. [Codeforces 452E] Three Strings

    [题目链接] https://codeforces.com/contest/452/problem/E [算法] 构建后缀数组 用并查集合并答案即可 时间复杂度 : O(NlogN) [代码] #in ...

  2. 【C++基础】重载,覆盖,隐藏

    函数签名的概念 函数签名主要包括1.函数名:2.参数列表(参数的个数.数据类型和顺序):但是注意,C++官方定义中函数签名不包括返回值!! 1.重载 函数重载是指在同一作用域内,可以有一组具有相同函数 ...

  3. BZOJ3127:[USACO2013OPEN]Yin and Yang

    浅谈树分治:https://www.cnblogs.com/AKMer/p/10014803.html 题目传送门:https://www.lydsy.com/JudgeOnline/problem. ...

  4. poj2584T-Shirt——网络最大流

    题目:http://poj.org/problem?id=2584 以人和衣服作为点,建立超级源点和超级汇点,人连边权为1的边,衣服对源点连边权为件数的边(别弄乱顺序): 试图写构造函数,但CE了,最 ...

  5. C# Unit Test 备注

    1. UT工程的编译一定要让依赖的dll在同一目录,即和测试目标dll运行的环境一样. 比如 Demo-UT测试Demo工程, 则Demo工程依赖的所有dll必须和Demo输出的可执行环境Demo.d ...

  6. WPF Get Multibinding Expression, Update Source,

    wpf 拿到某个control的multibinding以及其中每个Binding 1. 拿到multibinding      MultiBindingExpression mbe = Bindin ...

  7. java 基础知识学习 内存泄露(memory leak) VS 内存溢出(out of memory)以及内存管理

    内存泄露(memory leak) VS 内存溢出(out of memory) 内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory ...

  8. mvc 自定义 AuthorizeAttribute 验证逻辑

    public class AuthorizationFilterAttribute : AuthorizeAttribute { Dictionary<string, string> ro ...

  9. [hiho1584]Bounce

    题意:找出图中经过一次的格子个数. 解题关键: 组合数学的思想:先找出总的经过格子的次数,然后减去2倍的经过2次的格子个数. 1.总的求法:将长延展,当延展到n倍时,能够恰好到达右边的两个端点,则总格 ...

  10. CodeForces 484B Maximum Value (数学,其实我也不知道咋分类)

    B. Maximum Value time limit per test 1 second memory limit per test 256 megabytes input standard inp ...