第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)目标检测中的问题描述和R-CNN算法

    目标检测(object detection)是计算机视觉中非常具有挑战性的一项工作,一方面它是其他很多后续视觉任务的基础,另一方面目标检测不仅需要预测区域,还要进行分类,因此问题更加复杂.最近的5年使 ...

  2. 大叔学ML第一:梯度下降

    目录 原理 实践一:求\(y = x^2 - 4x + 1\)的最小值 实践二:求\(z = x^2 + y^2 + 5\)的最小值 问答时间 原理 梯度下降是一个很常见的通过迭代求解函数极值的方法, ...

  3. Material Design 开源项目总结

    Android开发中,我们不免会遇到Material Design展示的需求,以下是本人之前star的开源项目,供大家参考: 一.RippleEffect 项目地址:https://github.co ...

  4. 使用token和redis怎样判断账户是否失效和异地登录

    思路: 将token作为value,账户的id作为key 每次登录都去redis中查询该账户的登录是否过期,没有过期则删掉原来的id,token,将新生成token作为value存入redis中.过期 ...

  5. JavaScript中的注释问题详解? 部分3

    注释:解释代码的含义,浏览器中不执行. 方便其他程序员了解代码 ,也可以注释自己不需要的代码(开发过程中)! 1. 单行注释 // 用于一行代码上面 2.多行注释 /* */ 用于一段代码上面 或者是 ...

  6. swiper在vue项目中的循环轮播bug以及点击事件

    一般的,如果是静态数据(本地数据),可以直接在mounted生命周期中初始化,循环轮播.自动播放都比较正常. 但是,如果是动态从后台获取数据的话,采用上述方法会发现,轮播图无法自动播放,也无法拖拽. ...

  7. 今日头条面试题——LRU原理和Redis实现

    很久前参加过今日头条的面试,遇到一个题,目前半部分是如何实现 LRU,后半部分是 Redis 中如何实现 LRU. 我的第一反应应该是内存不够的场景下,淘汰旧内容的策略.LRU ... Least R ...

  8. 在koa中想要优雅的发送响应?看这就对了

    背景 前不久把项目中用了很久的一个伪中间件撸成了一个npm包发布了出去. 为什么叫伪中间件?正常的中间件的引用方式, 就拿body-parser为例. var Koa = require('koa') ...

  9. 向上造型中让我入坑的地方 (><)

    今天周六,闲着蛋疼就报名参加了公司的一个java比赛,比赛地点某某大厦11楼会议室,想象中应该是能容纳上百人的超大会议室.没成想,到地方一看,只是一个能容纳六七人的有圆形会议桌的小会议室.不过这仍然不 ...

  10. iOS逆向开发(0):修改二进制代码与重签名 | hopper | codesigh

    小白:小程,你知道有些iOS程序是没人性的吗?老是不按我的意愿来运行! 小程:我怎么知道你的意愿就是有人性的? 本文解决一个问题:修改别人的二进制程序并运行起来. 让别人的程序按你的意愿来运行,文明一 ...