第66条: 同步访问共享可变的数据

  所谓同步指的发出一个调用时,如果没有得到结果就不返回,直到有结果后再返回。另外相对应的是异步,指的是发出一个调用时就立即返回而不在乎此时有没有结果。

  同步和异步关注的是“消息通信机制”,通常我们提到同步的时候实际上只理解了它一部分或者干脆理解为“互斥”,这是不全对的,例如Java中synchronized关键字,经常听到教育我们说,要互斥访问某个共享变量且需要保证它线程安全的时候就用synchronized关键字。

  互斥表示当一个对象被一个线程修改的时候,可以阻止另一个线程观察到对象内部不一致的状态。同步不仅包含这层意义还包含:它可以保证进入同步方法或者同步代码块的每个线程,都看到由同一个锁保护的之前所有修改效果。

  对于synchronized我相信几乎人人都知道它是线程安全的重要保证,这里不再叙述它如何保证。着重强调几个术语:活性失败:线程A对某变量值的修改,可能没有立即在线程B体现出来。这是由于Java内存模型造成的原因,一个线程修改某个变量后并不会立即写入主存而是写到线程自身所维护的内存中,这个时候导致另一个线程从主存中取出的值并不是最新的,使用synchronized可保证这种可见性,当然还有volatile关键字。安全性失败:例如i++操作并不是原子的,而是先+1再复制,这就有两个动作,而这两个动作的完成很有可能导致中间穿插两个线程,这个时候就会导致程序计算结果出错。

一个活性失败的例子:

public class StopThread {
private static boolean stopRequested; public static void main(String[] args) throws InterruptedException {
Thread backgroundThread = new Thread(new Runnable() {
@Override
public void run() {
int i = 0;
while (!stopRequested){
i++;
}
}
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
stopRequested = true;
}
}

这个程序永远不会停止。因为虚拟机的优化,stopRequested值的变化不能马上被另一线程获得。修正这个问题的一种方式是同步访问stopRequested域,或用volatile修饰该域。

  简而言之,当多个线程共享可变数据的时候,每个读或者写数据的线程都必须执行同步,以确保线程安全,程序正确运行。

第67条: 避免过度同步

对于在同步区域的代码,千万不要擅自调用其他方法,特别是会被重写的方法,因为这会导致你无法控制这个方法会做什么,严重则有可能导致死锁和异常。通常,应该在同步区域内做尽可能少的工作。获得锁,检查共享数据,根据需要转换数据,然后放掉锁。

举个例子加深理解:

public interface SetObserver<E> {
//invoke when an element is added to the observable set
void added(ObservableSet<E> set, E element);
}
//Broken - invokes alien method from synchronized block!
public class ObservableSet<E> extends ForwardingSet<E> {
public ObservableSet(Set<E> set){
super(set);
}
private final List<SetObserver<E>> observers = new ArrayList<>();
public void addObserver(SetObserver<E> observer){
synchronized (observers){
observers.add(observer);
}
}
public boolean removeObserver(SetObserver<E> observer){
synchronized (observers){
return observers.remove(observer);
}
}
public void notifyElementAdded(E element){
synchronized (observers){
for(SetObserver<E> observer : observers){
observer.added(this, element);
}
}
}
/*
*解决ConcurrentModificationException和死锁的方案
*
public void notifyElementAdded(E element){
List<SetObserver<E>> snapshot = null;
synchronized (observers){
snapshot = new ArrayList<>(observers);
}
for(SetObserver<E> observer : snapshot){
observer.added(this, element);
}
}
*/
@Override
public boolean add(E e) {
boolean added = super.add(e);
if(added){
notifyElementAdded(e);
}
return added;
} @Override
public boolean addAll(Collection<? extends E> c) {
boolean result = false;
for (E element : c){
result |= add(element);
}
return result;
} public static void main(String[] args) {
ObservableSet<Integer> set = new ObservableSet<>(new HashSet<Integer>());
//java.util.ConcurrentModificationException
set.addObserver(new SetObserver<Integer>() {
@Override
public void added(ObservableSet<Integer> set, Integer element) {
System.out.println(element);
if(element == 23){
set.removeObserver(this);
}
}
});
//会发生死锁
/* set.addObserver(new SetObserver<Integer>() {
@Override
public void added(final ObservableSet<Integer> set, Integer element) {
System.out.println(element);
if(element == 23){
ExecutorService executor = Executors.newSingleThreadExecutor();
final SetObserver<Integer> observer = this;
try {
executor.submit(new Runnable() {
@Override
public void run() {
set.removeObserver(observer);
}
}).get();
} catch (InterruptedException e) {
throw new AssertionError(e.getCause());
} catch (ExecutionException e) {
throw new AssertionError(e.getCause());
}finally {
executor.shutdown();
}
}
}
});*/
for (int i = 0; i < 100; i++){
set.add(i);
}
}
}

第68条: executor和task优先于线程

之所以推荐executor和task原因就在于这样便于管理。

第69条: 并发工具优先于wait和notify

随着JDK的发展,基于原始的同步操作wait和notify已不再提倡使用,因为基础所以很多东西需要自己去保证,越来越多并发工具类的出现应该转而学习如何使用更为高效和易用的并发工具。

第70条: 线程安全性的文档化

书中提到了很有意思的情景,有人会下意识的去查看API文档此方法是否包含synchronized关键字,如不包含则认为不是线程安全,如包含则认为是线程安全。实际上线程安全不能“要么全有要么全无”,它有多种级别:

  不可变的——也就是有final修饰的类,例如String、Long,它们就不用外部同步。

  无条件的线程安全——这个类没有final修饰但其内部已经保证了线程安全例如并发包中的并发集合类同样它们无需外部同步

  有条件的线程安全——这个有的方法需要外部同步,而有的方法则和“无条件的线程安全”一样无需外部同步。

  非线程安全——这就是最“普通”的类了,内部的任何方法想要保证安全性就必须要外部同步。

  线程对立的——这种类就可以忽略不计了,这个类本身不是线程安全,并且就算外部同样同样也不是线程安全的,JDK中很少很少,几乎不计,自身也不会写出这样的类,或者也不要写出这种类。

可见队员线程是否安全不能仅仅做安全与不安全这种笼统的概念,更不能根据synchronized关键字来判断是否线程安全。你应该在文档注释中注明是以上哪种级别的线程安全,如果是有条件的线程安全不仅需要注明哪些方法需要外部同步,同时还需要注明需要获取什么对象锁。

第71条: 慎用延迟初始化

  延迟初始化又称懒加载或者懒汉式,这在单例模式中很常见。

  众所周知单例模式大致分为懒汉式和饿汉式,这两种方式各有其优缺点。对于饿汉式会在初始化类或者创建实例的时候就进行初始化操作,而对于懒汉式则相反它只有在实际用到访问的时候才进行初始化。

  至于用何种方式通常来讲并没有太大的讲究,几乎是看个人习惯。而此书却单独列了一条来说明延迟初始化使用不当所带来的危害。

  1) 使用延迟初始化时一定要考虑它的线程安全性,通常此时会利用synchronized进行同步。

  2) 若需要对静态域使用延迟初始化,且需要考虑性能,则使用lazy initialization holder class模式:

  public class Singleton {
private static class SingletonHolder {
private static Singleton singleton = new Singleton();
} private Singleton() { } public static Singleton getInstance() {
return SingletonHolder.singleton;
}
}

  这种模式不同于传统的延迟加载,当调用getInstance时候第一次读取SingletonHolder.singleton,导致SingletonHolder类得到初始化,这个类在装载并被初始化的时候会初始化它的静态域即Singleton实例,getInstance方法并没有使用被synchronized同步,并且只是执行一个域的访问这种延迟初始化的方式实际上并没有增加任何访问成本。

  3) 若需要对实例域使用延迟初始化,且需要考虑性能,则使用双重检查模式,这种模式其实也是为了避免synchronized带来的锁定开销:

  public class Singleton {
private volatile Singleton singleton; private Singleton() { }
public Singleton getInstance() {
Singleton result = singleton;
if (result == null) {
synchronized (this) {
result = singleton;
if (result == null) {
singleton = result = new Singleton();
}
}
}
return result;
}
}

  通常用的比较多的可能是对静态域应用双重检查模式。

  最后书中的建议就是正常地进行初始化,而对于延迟初始化则徐亚慎重考虑它的性能和安全性。

第72条: 不要依赖线程调度

不要依赖指的是不要将正确性依赖于线程调度器。例如:调整线程优先级,线程的优先级是依赖于操作系统的并不可取;调用Thrad.yield是的线程获得CPU执行机会,这也不可取。所以不要将程序的正确性依赖于线程调度器。

第73条: 避免使用线程组

ThreadGroup之前都没听过,反正不要用就是的了。

9.并发_EJ的更多相关文章

  1. .Net多线程编程—并发集合

    并发集合 1 为什么使用并发集合? 原因主要有以下几点: System.Collections和System.Collections.Generic名称空间中所提供的经典列表.集合和数组都不是线程安全 ...

  2. [ 高并发]Java高并发编程系列第二篇--线程同步

    高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...

  3. [高并发]Java高并发编程系列开山篇--线程实现

    Java是最早开始有并发的语言之一,再过去传统多任务的模式下,人们发现很难解决一些更为复杂的问题,这个时候我们就有了并发. 引用 多线程比多任务更加有挑战.多线程是在同一个程序内部并行执行,因此会对相 ...

  4. 关于如何提高Web服务端并发效率的异步编程技术

    最近我研究技术的一个重点是java的多线程开发,在我早期学习java的时候,很多书上把java的多线程开发标榜为简单易用,这个简单易用是以C语言作为参照的,不过我也没有使用过C语言开发过多线程,我只知 ...

  5. 如何在高并发环境下设计出无锁的数据库操作(Java版本)

    一个在线2k的游戏,每秒钟并发都吓死人.传统的hibernate直接插库基本上是不可行的.我就一步步推导出一个无锁的数据库操作. 1. 并发中如何无锁. 一个很简单的思路,把并发转化成为单线程.Jav ...

  6. Java多线程基础——对象及变量并发访问

    在开发多线程程序时,如果每个多线程处理的事情都不一样,每个线程都互不相关,这样开发的过程就非常轻松.但是很多时候,多线程程序是需要同时访问同一个对象,或者变量的.这样,一个对象同时被多个线程访问,会出 ...

  7. 多线程的通信和同步(Java并发编程的艺术--笔记)

    1. 线程间的通信机制 线程之间通信机制有两种: 共享内存.消息传递.   2. Java并发 Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式执行,通信的过程对于程序员来说是完全透 ...

  8. 伪共享(false sharing),并发编程无声的性能杀手

    在并发编程过程中,我们大部分的焦点都放在如何控制共享变量的访问控制上(代码层面),但是很少人会关注系统硬件及 JVM 底层相关的影响因素.前段时间学习了一个牛X的高性能异步处理框架 Disruptor ...

  9. 编写高质量代码:改善Java程序的151个建议(第8章:多线程和并发___建议126~128)

    建议126:适时选择不同的线程池来实现 Java的线程池实现从根本上来说只有两个:ThreadPoolExecutor类和ScheduledThreadPoolExecutor类,这两个类还是父子关系 ...

随机推荐

  1. 升讯威微信营销系统开发实践:(1)功能概要与架构设计( 完整开源于 Github)

    GitHub:https://github.com/iccb1013/Sheng.WeixinConstruction因为个人精力时间有限,不会再对现有代码进行更新维护,不过微信接口比较稳定,经测试至 ...

  2. 你可以这么理解五种I/O模型

    因为项目需要,接触和使用了Netty,Netty是高性能NIO通信框架,在业界拥有很好的口碑,但知其然不知其所以然. 所以本系列文章将从基础开始学起,深入细致的学习NIO.本文主要是介绍五种I/O模型 ...

  3. Spring源码追踪1——doGetBean(为什么org.springframework.data.redis.core.RedisTemplate的实例可以注入为ListOperations)

    类org.springframework.beans.factory.support.AbstractBeanFactory方法T doGetBean(final String name, final ...

  4. Python编程练习:简单的闹钟提醒

    问题详情:当前时间为下午2点,你在手机上设置了一个闹钟提醒,10000秒后触发该闹钟,请问闹钟铃声响起时的具体时间?请用print打印出时间 源码: a = 10000 h,m,s=2,0,0 if ...

  5. 7.数据库、Contentobserver

    群组页是程序内部维护的一个数据库,其中一张表groups,用于存放创建的群组,还有一张表thread_group,用于关联群组和系统短信数据库中的会话. 数据库应该这样设计 MySqliteHelpe ...

  6. Web Components(续)

    概述 之前我们介绍了Web Components的基本概念,现在我们给出一个使用Web Components的实例代码,并且对组件化进行一些思考.记录下来,供以后开发时参考,相信对其他人也有用. 实例 ...

  7. JavaScript 高性能数组去重

    中午和同事吃饭,席间讨论到数组去重这一问题 我立刻就分享了我常用的一个去重方法,随即被老大指出这个方法效率不高 回家后我自己测试了一下,发现那个方法确实很慢 于是就有了这一次的高性能数组去重研究 一. ...

  8. SVG之颜色、渐变和笔刷的使用

    一.颜色 我们之前使用英文来表示颜色并进行填充,比如: <circle cx="800" cy="120" r="110" strok ...

  9. python pip 安装库文件报错:pip install ImportError: No module named _internal

    解决方法: pip2.7, you can at first curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py then python2. ...

  10. WHERE 子句用于规定选择的标准

    WHERE 子句 如需有条件地从表中选取数据,可将 WHERE 子句添加到 SELECT 语句. (也称条件查询语句) 语法SELECT 列名称 FROM 表名称 WHERE 列 运算符 值 下面的& ...