3.1 可见性

程序清单3-1 在没有同步的情况下共享变量(不要这么做)

/**
* 主线程和读线程都将访问共享变量:ready 和 number
* 结果可能
* 1、 主线程先运行完,读线程后运行:读线程在控制台打印输出42,
* 2、 读线程先执行完,主线程后执行:读线程在控制台打印输出0
* 3、 NoVisibility根本无法终止,读线程可能永远都看不到ready的值
* 4、 重排序(Reordering):读线程看到了主线程中顺序靠后写入的ready的值,但没看到主线程先写入的number的值
*/
@NotThreadSafe
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;
}
}

程序清单3-2  非线程安全的可变整数类

//mutable 英[ˈmju:təbl] adj.    易变的,性情不定的;
@NotThreadSafe
public class MutableInteger { private int value; public int getValue() { return value; } public void setValue(int value) { this.value = value; }
}

程序清单3-3  线程安全的可变整数类

@ThreadSafe
public class SynchronizedInteger { @GuardedBy("this")private int value; public synchronized int getValue() { return value; } public synchronized void setValue(int value) { this.value = value; }
}

程序清单3-4 数绵羊

/**
* volatile通常用作某个操作完成、发生中断或者状态的标志
* volatile语义不足以确保递增操作(count++)操作的原子性
* asleep 状态量一旦在某个线程中被更改了,其它线程也应该看到
* */
@ThreadSafe
public class CountSheep { volatile boolean asleep; public void countSomeSheep() {
while(!asleep) {
for (int i = 0; i < 100; i++) {
System.out.println("sheep"+i);
}
}
}
}

3.2 发布与逸出

程序清单3-5   发布一个对象

/**
* “发布”一个对象的意思是:是对象能够在当前作用域之外的代码中使用。
* 1、将一个指向该对象的引用保存到其他代码可以访问的地方
* 2、在某一个非私有的方法中返回该引用
* 3、将引用传递到其他类的方法中
* @author guchunchao
*
*/
public class PublicObject { private static Set<Object> knownSecret; public void initialize() {
knownSecret = new HashSet<>(); //将方法体中new的对象发布到了方法体外的变量中
}
}

程序清单3-6  使内部的可变状态逸出(不要这么做)

/**
* 按如下方式发布states,任何修改者都能修改这个数组的内容,不安全
* @author gcc
*/
public class UnsafeStates {
private String[] states = {"aaa","bbb","ccc"}; public String[] getStates() { return states; }
}

程序清单3-7  隐式地使this引用逸出(不要这么做)

public class ThisEscape {
public ThisEscape(EventSource source) {
source.registerListener(
new EventListener() { //发布EventListener时,也隐含的将ThisEscape实例本身:this发布了
public void onEvent(Event e) {
doSomething(e);
}
}
);
}
}

程序清单3-8  使用工厂方法来防止this引用在构造过程中逸出

public class SafeListener {
private final EventListener listener;
private ThisEscape(EventSource source) {
listener = new EventListener(
public void onEvent(Event e) {
doSomething(e);
}
);
} public static SafeLisener newInstance(EventSource source) {
SafeLisener safe = new SafeLisener();
soure.registerListener(safe.listener);
} }

3.3  线程封闭

程序清单3-9 基本类型的局部变量与引用变量的线程封闭性

/**
* 栈封闭
* 由于任何方法都无法获得对基本类型的引用,因此基本类型的局部变量始终封闭在线程内
* @author guchunchao
*
*/
public class BasicTypeStackClose {
//candidate [ˈkændɪdət] 报考者; 申请求职者; 攻读学位者; 最后命运或结局如何已显然可见者;
public int loadTheArk(Collection<Object> candidates) {
SortedSet<Object> animals;
int numPairs = 0; //方法局部变量,始终在栈中
Object candidate = null; animals = new TreeSet<Object>();
animals.addAll(candidates); ++numPairs;
return numPairs; //即使被return了外界也无法修改。
}
}

程序清单3-10  使用ThreadLocal来维持线程封闭性

/**
* ThreadLocal对象通常用于防止对可变的、非线程安全的单实例变量(Singleon)或全局变量进行共享。
* ThreadLocal的get与set等访问接口或方法为每个使用该变量的线程都存有一份独立的副本,因此get总是能返回当前线程在调用set时设置的新值。
*
* 单线程应用程序中维持一个全局的数据库连接,并在程序启动时初始化,从而避免在调用每个方法时都要传递一个Connection对象。
*
* 而JDBC的连接对象不一定是线程安全的,因此当多线程应用程序在没有协同的情况下使用全局变量时,就不是线程安全的。
* 将JDBC连接保存到ThreadLocal对象中,每个线程都会拥有属于自己的JDBC连接。
*
* 更多ThreadLocal内容见:http://www.cnblogs.com/dolphin0520/p/3920407.html
*/
public class ThreadLocalTemplate {
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection("DB_URL"); //JDBC的Connection不一定是线程安全的,封装到ThreadLocal中保证每个线程都会拥有自己的JDBC连接对象副本
}
}; public static Connection getConnection() {
return connectionHolder.get();
}
}

3.4  不变性

不可变对象一定是线程安全的! Why?

程序清单3-11 在不可变对象基础上构建不可变类

/**
* final修饰的类:
* 从字面意思来理解就是不会发生变化的类,那么是什么不会发生变化呢,其实就是类的状态,也就是不变类的实例一旦被创建,其状态就不会发生变化。
* 举个例子:
* 如果人是一个class,那么我们中的每一个都是人这个类的具体的instance,
* 如果人这个类只有一个状态就是生身父母,那么它就是一个不变类,因为每一个人在出生的那一刹那,生身父母就已经被设置了值,而且终生都不会发生变化。
* 不变类有什么好处呢?
* 1) 不变类是线程安全的,由于不变类的状态在创建以后不再发生变化,所以它可以在线程之间共享,而不需要同步。
* 。。。。*/
@Immutable //[ɪˈmju:təbl]不可改变的;
public final class ThreeStooges { private final Set<String> stooges = new HashSet<>();//[stu:dʒ]喜剧里的配角或丑角,助手,伙伴; public ThreeStooges() {
stooges.add("hello");
stooges.add("world");
stooges.add("hello world");
} public boolean isStooge(String name) {
return stooges.contains(name);
}
}

程序清单3-12  对数值及因数分解结果进行缓存的不可变容器类

/**
* 对于在访问和更新多个相关变量是出现的竞态条件问题,可以通过将这些变量全部保存在一个不可变对象中来消除。
* 如果是一个可变的对象,那么就必须使用锁来确保原子性。
* 如果是一个不可变对象,那么当线程获得了对该对象的饮用后,就不必担心另一个线程会修改对象的状态。
* 如果要更新这些变量,可以创建一个新的容器对象,但其他使用原有对象的线程仍然会看到对象处于一致的状态。
* @author guchunchao
*
*/
@Immutable
public class OneValueCache {//存缓存的容器类
private final BigInteger lastNumber;
private final BigInteger[] lastFactors;
public OneValueCache(BigInteger lastNumber, BigInteger[] lastFactors) {
this.lastNumber = lastNumber;
this.lastFactors = lastFactors;
}
public BigInteger[] getLastFactors(BigInteger i) {
if(lastNumber == null || lastNumber.equals(i))
return null;
else
return Arrays.copyOf(lastFactors, lastFactors.length);
}
} //程序清单3-13 使用指向不可变容器对象的 volatile类型引用以缓存最新结果
@ThreadSafe
public class VolatileCacheFactorizer implements Servlet { private volatile OneValueCache cache = new OneValueCache(null, null); @Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
BigInteger i = extractFromReqest(req);
BigInteger[] factors = cache.getLastFactors(i);
if(factors == null) {
factors = factor(i);
cache = new OneValueCache(i, factors);
}
encodeIntoResponse(res,factors);
}
}

3.5 安全发布

程序清单3-14    在没有足够同步的情况下发布对象(不要这么做)

//不安全的发布
public Holder holder; public void initialize() {
holder = new Holder(42);
}

  

程序清单3-15 由于未被正确发布,因此这个类可能出现故障

//不安全的发布
public class Holder {
private int n;
public Holder(int n) {
this.n = n;
}
   public void assertSanity() {
if(n != n)
      throw new AssertionError("This Statement is false.");
   }
}

  

  

《JAVA并发编程实战》示例程序 第三章的更多相关文章

  1. java并发编程实战笔记---(第三章)对象的共享

    3.1 可见性 synchronized 不仅实现了原子性操作或者确定了临界区,而且确保内存可见性. *****必须在同步中才能保证:当一个线程修改了对象状态之后,另一个线程可以看到发生的状态变化. ...

  2. java并发编程实战:第十二章---并发程序的测试

    并发程序中潜在错误的发生并不具有确定性,而是随机的. 安全性测试:通常会采用测试不变性条件的形式,即判断某个类的行为是否与其规范保持一致 活跃性测试:进展测试和无进展测试两方面,这些都是很难量化的(性 ...

  3. 《Java并发编程实战》第十二章 测试并发程序 读书笔记

    并发测试分为两类:安全性测试(无论错误的行为不会发生)而活性测试(会发生). 安全測试 - 通常採用測试不变性条件的形式,即推断某个类的行为是否与其它规范保持一致. 活跃性測试 - 包含进展測试和无进 ...

  4. java并发编程实战:第十四章----构建自定义的同步工具

    一.状态依赖性管理 对于单线程程序,某个条件为假,那么这个条件将永远无法成真 在并发程序中,基于状态的条件可能会由于其他线程的操作而改变 可阻塞的状态依赖操作的结构 acquire lock on o ...

  5. 《Java并发编程实战》第十五章 原子变量与非堵塞同步机制 读书笔记

    一.锁的劣势 锁定后假设未释放.再次请求锁时会造成堵塞.多线程调度通常遇到堵塞会进行上下文切换,造成很多其它的开销. 在挂起与恢复线程等过程中存在着非常大的开销,而且通常存在着较长时间的中断. 锁可能 ...

  6. 《Java并发编程实战》第十六章 Java内存模型 读书笔记

    Java内存模型是保障多线程安全的根基,这里不过认识型的理解总结并未深入研究. 一.什么是内存模型,为什么须要它 Java内存模型(Java Memory Model)并发相关的安全公布,同步策略的规 ...

  7. java并发编程实战笔记---(第四章)对象的组合

    4.1设计线程安全的类 包含三个基本要素: 1.找出构成对象状态的所有变量 2.找出约束状态变量的不变性条件 2.简历对象状态的并发访问管理策略 对象的状态: 域 基本类型所有域, 引用类型包括被引用 ...

  8. java并发编程实战:第十六章----Java内存模型

    一.什么是内存模型,为什么要使用它 如果缺少同步,那么将会有许多因素使得线程无法立即甚至永远看到一个线程的操作结果 编译器把变量保存在本地寄存器而不是内存中 编译器中生成的指令顺序,可以与源代码中的顺 ...

  9. java并发编程实战:第十五章----原子变量与非阻塞机制

    非阻塞算法:使用底层的原子机器指令(例如比较并交换指令)代替锁来确保数据在并发访问中的一致性 应用于在操作系统和JVM中实现线程 / 进程调度机制.垃圾回收机制以及锁和其他并发数据结构 可伸缩性和活跃 ...

  10. 《Java并发编程实战》第十四章 构建自己定义的同步工具 读书笔记

    一.状态依赖性的管理 有界缓存实现的基类 @ ThreadSafe public abstract class BaseBoundedBuffer<E> { @GuardeBy( &quo ...

随机推荐

  1. SQL 行转列 PIVOT 学习示例

    CREATE TABLE [StudentScores] ( ), --学生姓名 ), --科目 [Score] FLOAT, --成绩 ) select * from [StudentScores] ...

  2. java有关 String char 常见问题 编辑中

    1 输入输出有关 Scanner 的next()方法 返回值是String 所以尝试获得char时 应该用input.next().charAt[0] 2 空值 String 中null是指 对象引用 ...

  3. 英语-TOEFL和GRE复习计划与资料

    目录 一. TOEFL (1). 阅读: 60 minutes (2). 听力: 50 minutes (3). 口语: 20 minutes (4). 作文: 60 minutes 单词准备 其他资 ...

  4. 影响Linux发展的四位天才黑客

    影响Linux发展的四位天才黑客 相信大家对 Linux 再熟悉不过了.我们都知道 Linux继承自 Unix,但其实他们上一代还有一个 Multics.从最早的 Multics 发展到最早版本的 L ...

  5. 在source中查看代码

    如果你想查看网页的代码,有时会出现这种情况: o my god!啥都看不出来!这要怎么办?放弃吗,当然不啦! 点击这个神奇的按钮,奇迹发生了! 瞬间变成这个样子,哇,好激动!

  6. Linux 基础学习:文件权限与种类

    1.文件权限 linux系统中通过 “ls -al”,可查看当前目录的所有文件的详细信息. 第一列代表这个文件的类型与权限: 第一个字符表示文件类型: [d]:表示目录文件 [-]:表示普通文件 [l ...

  7. 算法学习之BFS、DFS入门

    算法学习之BFS.DFS入门 0x1 问题描述 迷宫的最短路径 给定一个大小为N*M的迷宫.迷宫由通道和墙壁组成,每一步可以向相邻的上下左右四格的通道移动.请求出从起点到终点所需的最小步数.如果不能到 ...

  8. 使用原生 JS 复制文本兼容移动端 iOS & android

    注意事项 使用 JS 实现复制功能并不是很难,但是有几个需要注意的地方. 首先文本只有选中才可以复制,所以简单的做法就是创建一个隐藏的 input,然后绑定需要复制的文本. 另外如果将 input 设 ...

  9. Codeforces Round #545 (Div. 2)-Camp Schedule

    题目要求,给定一个s序列,一个p序列,问能不能对s做相应的调整,使得s序列中,有尽可能多的p子串(可以重复) 最开始我拿到这个题目,也是一点头绪都没有,如何做调整呢? 首先考虑如何会有尽可能多的子串, ...

  10. Android Studio3.x上使用Lombok

    1.添加gradle依赖 implementation 'org.projectlombok:lombok:1.16.8' //添加lombok依赖implementation 'org.glassf ...