整理自java并发编程实践第12章 12.4测试方法补遗
不连贯的同步性:
为了同步某个对象或者对象本身的某个域的访问,使用同步锁(内部锁或者显式锁,例如,对象本身的内部锁)来保护同步对象.但是如果访问该同步对象没有一贯性地通过同步锁获得访问,就意味着同步策略没有一贯地执行.通俗地讲就是在访问同步对象的时候,采用了双重标准,既有同步的操作也有非同步的操作,破坏了同步的连贯性和完整性.
直接调用Thread.run
直接调用Thread的run方法几乎总是个错误,应该调用Thread.start
未释放的显式锁
不同于内部锁,显式锁在控制退出了它们被请求的访问的时候,不会自动地释放.标准的技巧是由final块中释放锁;否则如果产生异常,锁仍会保存未释放状态.
try{
}finally{
//release lock
}
空synchronized块
空的synchronized块在java存储模型下是有语义的,也是会被执行的.无论开发者试图去解决什么问题,通常会有更好的解决方案.
双重检查
在惰性初始化时候,双重检查作为降低同步开销的技巧是有缺陷的.涉及的问题是当读取一个共享可变域的时候,缺乏适当的同步.
双重检查产生的原因是早期jvm缓慢的无竞争同步和缓慢的jvm启动.
双重检查的运作方式是:首先检查在没有同步的情况下,能够被多个线程共享的resource是否被初始化,如果resource不为null就使用它,否则进入初始化的同步块,并再次检查resource是否为null.需要初始化.以便确保只有唯一的线程或者唯一的一次真正地初始化了多个线程共享的resource.通常的代码路径是:获取一个已经构建的resource的引用,并没有用到同步.这就是问题所在,当前线程可能看到一个部分创建的resource.
双重检查的真正问题在于:基于这样的一种假设:当没有使用同步时读取一个共享变量,可能发生的最坏的事情不过是错误地看到过期值(具体而言是null);在这种情况下,通过获取锁后再次检查一次,希望能避免之前看到过期值的风险.但是糟糕的情况是获取到对象的过期值是有效的(具体而言是非空),而当前对象的状态是无效或者错误的(例如是null).这样就会认为对象已经创建成功,而得到一个实际上无效,错误或者为null的resource引用.
public class UnsafeLazyInit {
private static UnsafeLazyInit resource;
private final static Object initLock = new Object();
private UnsafeLazyInit(){
}
public static UnsafeLazyInit getInstance(){
if(null == resource){
synchronized(initLock){
if(null == resource){
resource = new UnsafeLazyInit();
}
}
}
return resource;
}
}
Java5.0以后,如果把resource声明为volatile类型,双重检查方式就能够很好地工作,因为读取未经缓存过的resource引用.另外jvm做到读取volatile变量相比读取非volatile变量的性能开销并没有增加很多,因此这种方法的性能开销也很低.另外早期jvm缓慢的无竞争同步和缓慢的jvm启动的问题现在已经解决,这种优化的效果也越发不明显.
另外为了确保初始化的安全性,可以通过创建不可变的安全对象在没有同步的状态下,可以被安全地跨线程共享,而不用关心它们是何时发布的.
public class SafeLazyInit {
private volatile static SafeLazyInit resource;
private final static Object initLock = new Object();
private SafeLazyInit(){
}
public static SafeLazyInit getInstance(){
if(null == resource){
synchronized(initLock){
if(null == resource){
resource = new SafeLazyInit();
}
}
}
return resource;
}
}
一个正确创建的对象,任何可以通过final域访问到的对象或者对象中的域,对看到它的线程都是实时,可见的.另外对于含有final域的对象,final域的初始化安全性可以抑制重排序,否则这些重排序会发生在对象的构造期间以及内部加载对象引用的时刻.
final域的对象只有在对象构造函数完成时才是可见的,对于非final域的对象,或者创建完成后可能改变的值,必须使用同步策略来确保其可见性.
下面的代码是构造安全的,虽然states是使用非线程安全的hashmap,但是在构造器中仍然是安全的,因为states是final域的,并且构造器中的代码是没有重排序,串行执行的.states的引用直到构造器完成之后才对其他线程可见.
import java.util.HashMap;
import java.util.Map;
public class SafeStatus {
private final Map<String,String> states;
public SafeStatus(){
states = new HashMap<String,String>();
states.put("color", "blue");
states.put("length", "123");
states.put("width", "456");
states.put("weight", "789");
}
}
由构造函数中启动线程
由构造函数中启动线程,会引入子类化问题的风险,同时还会引起this引用由构造函数中发布溢出.
通知错误
notify和notifyAll方法预示着,一个对象的状态可能已经以某种方式发生改变,进而通知那些等待与相关对象关联的条件队列的线程解除堵塞,重新获取条件. 因此只有在与条件队列关联的状态发生改变后才应该去调用这些方法.否则在没有修改任何状态的情形下调用这些方法,通常是一种错误.
条件等待错误
在一个队列中等待时,Object.wait和Condition.wait不仅应该在持有正确的锁的情况下,在循环中被调用,而且要在测试过一系列谓词之后.在不持有锁,不在循环中或者没有测试某些状态的情形下调用Object.wait和Condition.wait,通常是一种错误.
休眠或者等待的时候持有锁
调用Thread.sleep的时持有锁,会导致其他的线程在很长的一段时间内无法执行,因此这是一个潜在的严重的活跃度危险.调用Object.wait和Condition.wait时持有锁也会导致同样的问题.
自旋循环
如果代码除了循环检查(忙等待)一个域是否有期望值之外,不做任何事情,就会浪费cpu时间,并且如果域不是volatile类型的,就无法保证循环检查可以终止,因为可能会持有一个过期的状态.如果等待一个状态装换的发生,采用闭锁或者条件等待通常是更好的技术
老实说java并发编程实践这本书的翻译质量的确不高,不过原著内容写的还是极其精彩.看来接下来要读一些java存储模型方面的内容了.
分享到:
相关推荐
SQL反模式是指在使用SQL进行数据库设计和编程时常见的错误做法或不良习惯。这些反模式在实践中可能会导致性能下降、数据不一致、难以维护等问题。以下是一些可能与“SQL反模式”相关的知识点: 1. SQL反模式的定义...
8. **线程安全问题**:并发编程时,不恰当的同步可能导致死锁、竞态条件等问题。理解线程安全的概念,熟练运用synchronized关键字和并发工具类,如Semaphore、CountDownLatch等,能有效避免这些问题。 9. **贫血...
Java反模式涵盖了许多主题,包括对象设计、类设计、并发处理、内存管理、异常处理等方面。例如,"Singleton"反模式展示了过度依赖单例类可能导致的测试困难和模块化问题;"God Class"反模式揭示了大而全的类如何破坏...
《Java 反模式 卷3》是一本深入探讨Java编程中常见错误做法的书籍,旨在帮助开发者识别并避免这些可能导致程序效率低下、可维护性差的编程习惯。书中的反模式涵盖了许多Java开发的关键领域,如设计、架构、并发以及...
《SQL反模式——逻辑数据库设计反模式》这本书深入探讨了在数据库设计中可能出现的问题和不良实践,这些问题通常被称为“反模式”。SQL反模式是数据库设计过程中的一些常见错误,这些错误可能导致性能下降、数据不...
《SQL反模式》是数据库设计和SQL编程领域中一本极具影响力的书籍,它深入探讨了在实践中容易出现的问题以及如何避免这些“反模式”。SQL反模式指的是在使用SQL进行数据库设计和查询时,可能导致性能低下、可维护性差...
Java并发编程涉及线程、锁、并发工具等,如synchronized关键字、ReentrantLock、CountDownLatch、CyclicBarrier等。线程池通过ExecutorService管理线程,提高资源利用率,可以通过ThreadPoolExecutor创建。 ...
高级java笔试题 《Java ...并发编程的难点在于,反常识!因为并发、并行本身,是有悖于我们大脑的工作模式的,也就是说,我们长期的写码不得不 而一旦将这一个一个的线程组合起来,奇妙的 bug 发生了...
其次,处理反斜杠的特殊含义,因为反斜杠在正则表达式中有转义的作用;再者,执行匹配操作,可以使用match()或search()等函数;最后,利用模块级函数来处理字符串,如分割和替换等。在编写正则表达式时,还需注意...
5. **并发编程**:在多线程环境下,Java提供了丰富的并发工具类,如synchronized关键字、Lock接口、ExecutorService等,帮助开发者处理线程安全问题,优化程序性能。 6. **泛型**:泛型引入后,Java的类型系统变得...
FindBugs的反模式指的是那些在代码中常见的、可能导致问题或者效率低下的编程习惯。 1. **空指针异常(NullPointerException)**:这是Java程序员最常遇到的错误之一。FindBugs能够检测到可能引发NPE的情况,比如对...
单例模式是软件设计模式中的一种,它的主要目的是确保一个类只有一个实例,并提供一个全局访问点。这种模式在很多场景下都非常有用,比如...建议进一步学习这个文件中的内容,以深化对Java并发编程和单例模式的理解。
首先,《Java开发手册》v1.5.0 华山版是一份全面详尽的Java编程指南,包含了命名规则、基本语法、异常处理、并发编程、日志记录、性能优化等多个方面的内容。以下是一些核心要点: 1. **命名规则**:变量、方法、类...
4. **数据序列化与反序列化**:在网络中传输对象时,需要将对象转换为可传输的字节流,这涉及到Java的序列化机制。通过实现Serializable接口,我们可以将Java对象转化为字节流在网络中传输。 5. **协议设计**:QQ...
7. **多线程和并发**:为了提高网络应用的性能,多线程和并发编程技术常常被用到。Delphi的TThread类使得创建和管理线程变得简单,而TTask和TParallel类则提供了高级并发功能。 8. **安全性**:在网络编程中,数据...
3. **多线程(Thread)**:Java提供内置支持进行并发编程,复习内容可能包括线程的创建(通过继承`Thread`类或实现`Runnable`接口)、线程同步(如`synchronized`关键字、`wait()`, `notify()`和`notifyAll()`方法)、...
总之,"JDA-java反编译"涉及到的知识点包括:Java反编译工具的使用、Java字节码原理、事件驱动编程、观察者模式、并发编程、网络通信API的封装以及代码优化。通过反编译JDA,我们可以深入理解这个库的工作原理,提升...
6. **多线程**:Java提供了内置的Thread类和Runnable接口来实现并发编程。实例可能包括同步机制(如synchronized关键字、wait()、notify())和线程池的使用。 7. **图形用户界面(GUI)编程**:Java的Swing或JavaFX...
6. **并发编程**:为了处理大量并发连接,服务器需要高效地管理多线程或多进程。书中会涵盖线程安全、同步原语、异步I/O和事件驱动编程等内容。 7. **安全性与反作弊**:网络游戏中,安全性和反作弊措施是必不可少...
例如,socket()函数用于创建一个socket描述符,bind()将套接字与特定的IP地址和端口号关联,listen()让服务器进入监听模式等待客户端连接,accept()接受客户端连接并返回新的套接字描述符,connect()则是客户端用来...