并发编程的第二部分,先来谈谈发布(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 - 434D Nanami's Power Plant

    Codeforces - 434D 题目大意: 给定一个长为n的序列,序列中的第i为上的值\(x_i\),序列第i位上的值\(x_i\in[l_i,r_i]\),价值为\(f_i(x_i)\),其中\ ...

  2. 1070 Bash 游戏 V4

    传送门 1070 Bash游戏 V4   基准时间限制:1 秒 空间限制:131072 KB 分值: 40 难度:4级算法题 有一堆石子共有N个.A B两个人轮流拿,A先拿.每次拿的数量最少1个,最多 ...

  3. BZOJ3938:Robot

    浅谈标记永久化:https://www.cnblogs.com/AKMer/p/10137227.html 题目传送门:https://www.lydsy.com/JudgeOnline/proble ...

  4. Hive安装&nbsp;&nbsp;&nbsp;&nbsp;…

    Hive安装 mysql使用主机(win7)上的mysql数据库,启动后,要关闭360和win7自带的防火墙,确保在虚拟机里能拼通主机********************************* ...

  5. 测试你开发的web系统在各种类型浏览器上的兼容性

    可以使用 https://www.browserstack.com 来测试你所开发的web系统在各种各样的浏览器,以及各种手机平台上的兼容性.

  6. 从零开始Spring项目

    Spring Boot是什么 SpringBoot是伴随着Spring4.0诞生的: 从字面理解,Boot是引导的意思,SpringBoot帮助开发者快速搭建Spring框架: SpringBoot帮 ...

  7. SQL Server(二)——语句 转

    表的创建: 1.创建列(字段):列名+类型 2.设置主键列(primary key):能够唯一标识一条数据 3.设置唯一(unique):内容不能重复 4.外键关系:一张表(从表)其中的某列引用自另外 ...

  8. SVN版本控制图标未显示或显示异常解决方法

    SVN版本控制图标未显示问题解决方法,刚开始遇到这个问题的时候,好苦恼.经过“千辛万苦”的努力,终于得以解决,分享给大家,希望能帮到各位哦! 工具/原料   SVN安装包 方法/步骤     首先安装 ...

  9. Codeforces Round #459 (Div. 2):D. MADMAX(记忆化搜索+博弈论)

    题意 在一个有向无环图上,两个人分别从一个点出发,两人轮流从当前点沿着某条边移动,要求经过的边权不小于上一轮对方经过的边权(ASCII码),如果一方不能移动,则判负.两人都采取最优策略,求两人分别从每 ...

  10. unite2017相关

    日程 http://unite2017.csdn.net/ http://www.sohu.com/a/137202360_280780 http://www.gameres.com/750046.h ...