第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. ASP.NET MVC项目中App_Code目录在程序应用

    学习ASP.NET MVC,如果你是开发ASP.NET MVC项目的,也许你去为项目添加前ASP.NET项目的APP_Code目录,在这里创建与添加的Class类,也许你无法在MVC项目所引用. 那这 ...

  2. python 字典详细使用

    1. 字典 字典是无序.可变序列. 定义字典时,每个元素的键和值用冒号分隔,元素之间用逗号分隔,所有的元素放在一对大括号“{}”中. 字典中的键可以为任意不可变数据,比如整数.实数.复数.字符串.元组 ...

  3. Ubuntu 16.04下Samba服务器搭建和配置(配截图)

    一.相关介绍 Samba是在Linux和UNIX系统上实现SMB协议的一个免费软件,由服务器及客户端程序构成.SMB(Server Messages Block,信息服务块)是一种在局域网上共享文件和 ...

  4. SpringCache学习实践

    1. SpringCache学习实践 1.1. 引用 <dependency> <groupId>org.springframework.boot</groupId> ...

  5. jsbridge的js封装

    /*注意:源生app需要配置jsbridge的环境,而在前端页面中需要下方封装代码,既可以达到调用app方法的功能和注册供app调用的方法1.注册方法:注册后,供app调用,注册时,同名函数,下一个会 ...

  6. 课程五(Sequence Models),第二 周(Natural Language Processing & Word Embeddings) —— 2.Programming assignments:Emojify

    Emojify! Welcome to the second assignment of Week 2. You are going to use word vector representation ...

  7. Redis主从+KeepAlived实现高可用

    Redis是我们当下比较流行使用的非关系数据库,可支持多样化的数据类型,多线程高并发支持,redis运行在内存拥有更快的读写.因为redis的表现如此出色,如何能保障redis在运行中能够应对宕机故障 ...

  8. sql server 索引阐述系列四 表的B-Tree组织

    一.概述 说到B-tree组织,就是指索引,它可以提供了对数据的快速访问.索引使数据以一种特定的方式组织起来,使查询操作具有最佳性能.当数据表量变得越来越大,索引就变得十分明显,可以利用索引查找快速满 ...

  9. gradle 自定义插件 下载配置文件

    1.新建Gradle项目: 2.建立src/main/groovy目录,并添加如下代码: ConfigPlugin.groovy package com.wemall.config import or ...

  10. Deeplearning.ai课程笔记--汇总

    从接触机器学习就了解到Andrew Ng的机器学习课程,后来发现又出来深度学习课程,就开始在网易云课堂上学习deeplearning.ai的课程,Andrew 的课真是的把深入浅出.当然学习这些课程还 ...