并发之共享资源
不正确的资源访问:一个线程可能会在另一个线程读写数据的时候也进行了读写
解决资源共享竞争:
基本上所有的并发模式在解决线程冲突问题的时候,都是采用序列化访问共享资源的方案。即给定时刻,只有一个任务能访问共享资源。
共享资源一般是以对象的形式存在的内存片段,但也可以是文件、输入/输出端口、或者是打印机。
- 加锁:锁语句产生了一种互相排斥的效果,又称为互斥量Mutex。
- synchronized:java的关键字,重量级锁。要控制对共享资源的访问,首先得将它包装进一个对象。然后把所有要访问该资源的方法标记为synchronized。
- 所有对象都含有单一的锁(monitor),当在对象上调用其任意synchronized方法时,此对象都被加锁。对于某个特定对象来说,其所有的synchronized方法共享同一把锁。
- 针对每个类,也有一个锁,作为类的Class对象的一部分。所以synchronized static方法可以在类的范围内防止对static数据的并发访问。
- 每个访问临界共享资源的方法都必须被同步,否则就不会被正确地工作。(一次仅允许一个进程使用的资源称为临界资源)
- 使用显示的Lock对象:java SE5定义在java.util.concurrent.locks中显示的互斥机制。Lock对象必须被显示地创建、锁定lock()和释放unlock()(在finally语句中)。
- 原子性:不能被线程调度机制中断的操作,一旦操作开始,那么它一定可以在可能发生的下一次上下文切换之前执行完毕。
- 除long和double之外的基本类型上的读取和写入都可以看做是原子性的。但是double和long是64位的,读取和写入可以当做两个32位的操作,因此可能在一个读取和写入中间发生上下文切换,从而导致不同任务可能看到不同的正确结果。
- volatile关键字确保了共享元素的可见性,因为对该域的写操作会刷新到主存中。volatile能保证它修饰的单一变量的读写具有原子性(包括double和long),但是对复合操作是不支持原子性的。并且为了实现严格的可见性,通过添加内存屏障来阻止指令序列的重排序。
- 同步操作也会导致向主存中刷新,因此同步方法块中的域不需要用volatile修饰。
- 原子类:javaSE5引入的,提供下面的形式的原子性条件更新操作:
boolean compareAndSet(expectedValue, updateValue) - 临界区:进程中用于实现进程互斥的那段代码称为临界区。同步方法块,可以使多个任务访问对象的时间性能提高。
- 线程本地存储(根除对变量的共享):为使用相同变量并且需要相同初始值的每个不同线程都创建不同的局部存储。
创建和管理线程本地存储可以用java.lang.ThreadLocal类实现。该类对象通常被当做静态域存储,在创建ThreadLocal后,只能通过get和set来读取写入。get获取的是与线程相关联对象的副本,而set是将对象插入到其线程存储的对象中,并返回存储中原有的对象。