一、对可共享数据的同步访问

synchronized关键字可以保证在同一时刻,只有一个线程在执行一条语句,或者一段代码块。正确地使用同步可以保证其他任何方法都不会看到对象处于不一致的状态中,还能保证通过一系列看似顺序执行的状态转变序列,对象从一种一致的状态变迁到另一种一致的状态。

迟缓初始化(lazy initialization)的双重检查模式(double-check idiom)

//The double-check idiom for lazy initializaation -broken!
private static Foo foo = null;
public statc Foo getFoo(){
if(foo == null){
synchronized(Foo.class){
foo = new Foo();
}
}
return foo;
}

该模式的背后思想是,在域(foo)被初始化之后,再要访问该域时无需同步,从而避免多数情况下的同步开销。同步只是被用来避免多个线程对该域做初始化。问题出在:在缺少同步的情况下, 读入一个已“发布”的对象引用并不保证:一个线程会看到在对象引用发布之前所有保持在内存中的数据。一般情况下, 双重检查模式并不正确的工作。但是如果被共享的变量包含一个原语值,而不是一个对象引用,则它可以正确的工作。

按需初始化容器类(initialize-on-demand holder class)模式:

// The initialize-on-demand holder class idiom
private static class FooHolder{
static final Foo foo = new Foo();
}
public static Foo getFoo(){
return FooHolder.foo;
}

该模式充分利用了Java语言中“只有当一个类被用到的时候它才被初始化”。该模式的优美之处在于,getFoo方法并没有被同步,它只执行一次域访问,所以迟缓初始化并没有引入实际的访问开销。这种模式的缺点在于,它不能用于实例域,只能用于静态域。

简而言之,无论何时当多个线程共享可变数据的时候,每个读或者写数据的线程必须获得一把锁。

二、避免过多的同步

    为了避免死锁的危险,在一个被同步的方法或者代码块中,永远不要放弃对客户的控制。换句话说,在一个被同的区域内部,不要调用一个可被改写的公有或受保护的方法(这样的方法是往往是抽象的,但偶尔它们也会有一个具体的默认实现).例如,在该方法中创建另一个线程,再回调到这个类中,然后,新建的线程试图获取原线程所拥有的那把锁,这样就导致新建线程被阻塞。如果方法还在等待这个线程完成任务,则死锁就形成了。

   通常,在同步区域内你应该做尽可能少的工作。获得锁,检查共享数据,根据需要转换数据,然后放掉锁。如果你必须要执行某些很耗时的动作,则应该设法把这个动作移到同步区域的外面。

为了避免死锁和数据破坏,千万不要从同步区域内部调用外来方法。

三、永远不要在循环的外面调用wait

Object.wait方法的作用是使一个线程等待某个条件,它一定是在一个同步区域中被调用的,而且该同步区域锁住了被调用的对象。下面是使用wait方法的标准模式:

synchronized(obj){
while(<condition does not hold>)
obj.wait();
//preform action appropriate to condition
}

总是使用wait循环模式来调用wait方法。永远不要在循环的外面调用wait。循环被用于在等待的前后测试条件。

作为一种优化,如果处于等待状态的所有线程都在等待同一个条件,而每次只有一个线程可以从这个条件中被唤醒,那么你可以选择调用notify而不是notifyAll。如果只有一个线程在一个特定的对象上等待,那么这两个条件很容易被满足。

关于使用notifyAll优先于notify的建议有一个告诫:虽然使用notifyAll不会影响正确性,但是会影响性能。当"特定状态"到来时,你唤醒每一个线程,那么你每次唤醒一个线程,总共n次唤醒。所以,选择的唤醒策略是非常重要的。

如果竞争某个特定状态的所有线程在逻辑上是等价的,那么,你必须谨慎地使用notify,而不是notifyAll。

四、不要依赖于线程调度器

当有多个线程可以运行时,线程调度器(thread scheduler)决定哪个线程将会运行,以及运行多长时间。任何一个合理的JVM实现在作出这样的决定的时候,都努力做到某种公正性,但是对于不同的JVM实现,其策略大相径庭。任何依赖于线程调度器而达到正确性或性能要求的程序,很可能是不可移植的。

编写健壮的、响应良好的、可移植的多线程应用程序的最好办法是,尽可能确保在任何给定时刻只有少量的可运行线程。这使得线程调度器没有更多的选择:它只需运行这些可运行的线程,直到它们不再可运行为止。保持可运行线程数量尽可能少的主要方法是,让每个线程做少量的工作,然后使用Object.wait等待某个条件发生,或者使用Thread.sleep睡眠一段时间。

如果一个程序因为某些线程无法像其他的线程那样获得足够的CPU时间,而不能工作,那么,不要企图通过Thread.yield来“修正”该程序。你可能会成功地让程序工作,但是从性能的角度来看,这样得到的程序是不可能移植的。

调整线程优先级(thread priorities),也可以算是一条建议。线程优先级是Java平台上最不可移植的特征了

对于大多数程序员来说,Thread.yield的惟一用途是在测试期间人为地增加一个程序的并发性。通过探查一个程序更大部分的状态空间,可以发现一些隐藏错误(bug),从而对系统的正确性增强信心。这项技术已经被证明对于找出“微妙的并发性错误"非常有效。

五、线程安全性的文档化

     在一个方法的声明中出现synchronized修饰符,这是一个实现细节,并不是导出的API的一部分。出现了synchronized修饰符并不一定表明这个方法是线程安全的,它有可能随着版本的不同而发生变化。

下面列表概括了一个类可能支持的线程安全性级别,虽然还没有被广泛接受:

非可变的(immutable)--这个类的实例对于其客户而言是不变的。所以,不需要外部的同步。这样的例子包括String、Integer和BigInteger。

线程安全的(thread-safe)--这个类的实例是可变的。但是所有的方法都包含足够的同步手段,所以,这些实例可以被并发使用,无需外部同步。例如,Random和java.util.Timer。

有条件的线程安全(conditionally thread-safe)--这个类(或者关联的类)包含有某些方法,它们必须被顺序调用,而不能收到其他线程的干扰,除此之外,这种线程安全级别与上一种情形(线程安全)相同。

线程兼容的(thread-compatiable)--在每个方法调用的外围使用同步,此时,这个类的实例可以被安全地并发使用。如ArrayList和HashMap。

线程对立的(thread-hostile)--这个类不能安全地被多个线程并发使用,即使所有的方法调用都被外部同步包围。根源在于:这个类的方法要修改静态数据,而这些静态数据可能会影响其他的线程。幸运的是,在Java平台库中,线程对立的类或者方法非常少。System.runFinalizersOnExit方法是线程对立的,但是已经被废弃了。

六、避免使用线程组

     线程组(thread group)的初衷是作为一个隔离(applet(小程序)的机制,当然是出于安全的考虑,它们并没有真正实现这个承诺,它们的安全重要性已经差到在Java 2平台安全模型的核心工作中不被提及的地步。

Effective java笔记7--线程的更多相关文章

  1. Effective Java笔记一 创建和销毁对象

    Effective Java笔记一 创建和销毁对象 第1条 考虑用静态工厂方法代替构造器 第2条 遇到多个构造器参数时要考虑用构建器 第3条 用私有构造器或者枚举类型强化Singleton属性 第4条 ...

  2. effective java笔记之java服务提供者框架

    博主是一名苦逼的大四实习生,现在java从业人员越来越多,面对的竞争越来越大,还没走出校园,就TM可能面临失业,而且对那些增删改查的业务毫无兴趣,于是决定提升自己,在实习期间的时间还是很充裕的,期间自 ...

  3. Effective java笔记(二),所有对象的通用方法

    Object类的所有非final方法(equals.hashCode.toString.clone.finalize)都要遵守通用约定(general contract),否则其它依赖于这些约定的类( ...

  4. java笔记--使用线程池优化多线程编程

    使用线程池优化多线程编程 认识线程池 在Java中,所有的对象都是需要通过new操作符来创建的,如果创建大量短生命周期的对象,将会使得整个程序的性能非常的低下.这种时候就需要用到了池的技术,比如数据库 ...

  5. effective java笔记之单例模式与序列化

    单例模式:"一个类有且仅有一个实例,并且自行实例化向整个系统提供." 单例模式实现方式有多种,例如懒汉模式(等用到时候再实例化),饿汉模式(类加载时就实例化)等,这里用饿汉模式方法 ...

  6. java笔记--守护线程的应用

    守护线程的应用 Java中的线程可以分为两类,即用户线程和守护线程.用户线程是为了完成任务,而守护线程是为其他线程服务 --如果朋友您想转载本文章请注明转载地址"http://www.cnb ...

  7. Effective java笔记(一),创建与销毁对象

    1.考虑用静态工厂方法代替构造器 类的一个实例,通常使用类的公有的构造方法获取.也可以为类提供一个公有的静态工厂方法(不是设计模式中的工厂模式)来返回类的一个实例.例如: //将boolean类型转换 ...

  8. Effective java笔记(九),并发

    66.同步访问共享的可变数据 JVM对不大于32位的基本类型的操作都是原子操作,所以读取一个非long或double类型的变量,可以保证返回的值是某个线程保存在该变量中的,但它并不能保证一个线程写入的 ...

  9. Effective java笔记(六),方法

    38.检查参数的有效性 绝大多数方法和构造器对于传递给它们的参数值都会有限制.如,对象引用不能为null,数组索引有范围限制等.应该在文档中指明所有这些限制,并在方法的开头处检查参数,以强制施加这些限 ...

  10. Effective java笔记(三),类与接口

    类与接口是Java语言的核心,设计出更加有用.健壮和灵活的类与接口很重要. 13.使类和成员的可访问性最小化 设计良好的模块会隐藏起所有的实现细节,仅使用API与其他模块进行通信.这个概念称为信息隐藏 ...

随机推荐

  1. iOS js oc相互调用(JavaScriptCore)(二)

    下来我们使用js调用iOS js调用iOS分两种情况 一,js里面直接调用方法 二,js里面通过对象调用方法 首先我们看第一种,直接调用方法. 其中用到了iOS的block 上代码 -(void)we ...

  2. VCL里为什么要用类函数代替API,为什么要用CM_消息代替虚函数

    之所以要用类函数代替API,是因为VCL对它做了一些包装,好在API起作用之前和之后做一些额外的事情:通知和判断等等.之所以类函数要包装一个CM_消息,是因为这样方便程序员(在调用类函数的基础上)截断 ...

  3. Android UI 之 Tab类型界面总结

    Android 程序中实现Tab类型界面很常见,本人在做项目的时候也经常用到,所以想在这里总结一下,实现tab类型界面的几种方式,供大家参考.如有不对之处,欢迎大家指正! 一.TabActivity ...

  4. Centos 用户组管理

    #组帐号管理 linux 组管理 =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--= 1.组的分类 私用组:只能包含一个用 ...

  5. Ubuntu 12.04 SSH 安装

    By default Ubuntu Desktop OS comes with ssh clientpackage. It does not include ssh server package wh ...

  6. 关于Netty4.x中文教程系列更新进度的说明和道歉

    最近一些事情.貌似发现很久没更新教程了.这里和大家说一声对不起.教程5的前半部分差不多年前就写好了.但是由于年前我在的项目组项目进度比较紧张.一直在加班.教程的后半部分就一直没有写.年后由于一些公司人 ...

  7. rc.local自启动学习(转)

    linux有自己一套完整的启动体系,抓住了linux启动的脉络,linux的启动过程将不再神秘. 本文中假设inittab中设置的init tree为: /etc/rc.d/rc0.d/etc/rc. ...

  8. ubuntu下搭建hive(包括hive的web接口)记录

    Hive版本 0.12.0(独立模式) Hadoop版本 1.12.1 Ubuntu 版本 12.10 今天试着搭建了hive,差点迷失在了网上各种资料中,现在把我的经验分享给大家,亲手实践过,但未必 ...

  9. beej's 网络编程 打包数据pack data

    7.4. Serialization—How to Pack Data It's easy enough to send text data across the network, you're fi ...

  10. Excel有用的宏

    =Index({"同事","同学","亲戚"},b3) 前面的array默认索引从1开始. 如果b3为1.而枚举数组是: 0=>同事, ...