以下内容是我在阅读《图解Java多线程设计模式》一书中的总结和思考
本章配语是:能通过这座桥的只有一个人
几个概念:
- SharedResource(共享资源):可以被多个线程访问的类,包含safeMethod和unsafeMethod。其中unsafeMethod就是在多线程环境下不安全的,需要同步的方法。
- Single Threaded Execution模式:同一时间内只能让一个线程执行处理。
在Single Threaded Execution模式中,满足以下全部条件时,死锁就会发生:
- 存在多个SharedResource角色
- 线程在持有某个SharedResource角色的锁的同时,还要获取其他SharedResource角色的锁才能完成任务
- 获取SharedResource角色的锁的顺序是对称相反的,例如先锁A再锁B,和先锁B再锁A,这样就是对称相反
以上条件有一个不满足,死锁就会破解。比如:
- 将多个SharedResource角色封装到一个类中,这样就变成只有一个SharedResource角色了
- 上锁的顺序全都一样,这样也不会发生死锁
注意,这只是针对Single Threaded Execution模式:同一时间只能让一个线程执行处理。而能够实现这样的关键就是对访问共享资源的方法(method)进行互斥处理,也就是synchronized。
所以,只要同一个类中的方法(method)标志了synchronized关键字,就说明这些方法(method)之间是互斥的。
可复用性和继承反常
如果子类能够访问SharedResource角色的字段,那么编写子类的开发人员就要注意,子类中的方法是否可以破坏安全性,因为子类对象可以随意访问SharedResource的字段。
对于多线程程序设计来说,继承会引起一些麻烦的问题,需要妥善处理。
临界区的大小和性能
临界区:上锁的区域,就是{}花括号之间的代码。
一般来说,临界区越大,性能就越低,发生线程冲突的概率就越高。
void method()
{
synchronized (this)
{
// 线程无关的代码
...
// 真正需要同步的代码
}
}
就以上代码,如果将线程无关的代码放到同步代码块中,那么明明不用等待就可以操作的代码却要花时间去等待,那么就会降低效率了。
synchronized在保护着什么
这个问题要考虑到两个方面,一个是要弄清保护什么,另一个是要确定保护成功。
保护的一般就是SharedResource,具体一点来说就是字段的值。
是否保护成功:
关于这个问题,是需要不断累积经验才能快速准确地判断的。比如说出现继承反常的情况(即父类字段可以被子类直接调用)。
又比如对于SharedResource,一般来说是要一起放到临界区之内的,如果出现类似如下代码,即SharedResource没有放在一起保护,这样也就没有意义了,达不到效果。
public synchronized void setName(String name)
{
this.name = name;
}
public synchronized void setAddress(String address)
{
this.address = address;
}
原子操作
是否是原子操作对于是否需要上锁有比较大的影响。如果一个方法里面只有一个原子操作,那么就没有必要上锁了。
一般来说:基本数据类型和引用操作都是原子操作,但:double和long的操作就不是原子操作。还有,++和--也不是原子操作。
Semaphore类
java.util.concurrent包提供了表示计数信号量的Semaphore类。
这个有空再贴代码上来吧