共享受限资源
@(并发)[java, 并发, Thinking in Java]
1. 解决共享资源竞争
基本所有的并发模式在解决线程冲突问题的时候,都是采用序列化访问共享资源的方案,通常这是通过在代码前面加上一条锁语句实现的,这种机制称为互斥量
1.1. synchronized
共享资源一般是以对象形式存在的内存片段,然后把所有要访问这个资源的方法标记为synchronized。所有方法都自动含有单一的锁。当在对象上调用其任意synchronized方法时,此对象都被加锁,这是该对象上的其它synchronized方法只有等到前一个方法调用完毕并释放了锁之后才能被调用。
针对类,也有锁,所以synchronized static 方法可以在类的范围内防止对static数据的并发访问
每个访问临界资源的方法都必须同步
1.2. 显式的Lock对象
private Lock lock = new ReentrantLock();
public void next(){
lock.lock();
try{
//
}finally{
lock.unlock();
}
}
方法中紧接着对Lock的调用,必须放置在finally子句中带有unlock()的try-finally语句中
Lock()在加锁和释放锁方面,相对于内建的synchronized锁来说,赋予了更细粒度的控制力,如ReentrantLock允许尝试着获取但最终未获取锁,可以决定离开去执行其它事情,而不是等待直至这个锁被释放。
2. 原子性与易变性
原子操作是不能被线程调度机制中断的操作,原子性可以应用于除long和double之外的所有基本类型之上的简单操作。
JVM可以将64位的的读取和写入当作两个分离的32位操作来执行,这有时称为字撕裂,使用volatile就会获得原子性。
volatile关键字确保了应用的可视性。如果将一个域声明为volatile的,那么只要对这个域产生了写操作,那么所哟的读操作都可以看到这个修改。因为volatile会立即被写入到主存中,而读操作就发生在主存中。
同步会导致向主存中刷新,因此如果一个域完全由synchronized方法或语句块防护,就不必将其设置为volatile的。
使用volatile的唯一安全的情况是类中只有一个可变的域,如果一个域可能会被多个任务同时访问,或者这些任务中至少有一个是写入任务,那么就应该将这个域设置为volatile的
3. 原子类
AtomicInteger, AtomicLong, AtomicReference等
boolean compareAndSet(expectedValue, undateValue)
: 原子性条件更新操作
这些类被调整为可以使用在某些现代处理器上的可获得的、并且在机器级别的原子性
4. 临界区
临界区:防止多个线程同时访问方法内部的部分代码
synchronized(syncObjct){
//
}
称为同步控制块。在进入此段代码前必须得到syncObject对象的锁。
注意;synchronized关键字不属于方法特征签名的组成部分,所以可以在覆盖方法的时候加上。
5. 在其他对象上同步
在当前对象上同步:synchronized(this)
在其他对象上同步
private Object object = enw Object();
synchronized(object){
}
6. 线程本地存储
防止任务在共享资源上产生冲突的第二种方法:根除对变量的共享—-线程本地存储
java.lang.ThreadLocal
ThreadLocal类用于创建一个线程本地变量
在Thread中有一个成员变量ThreadLocals,该变量的类型是ThreadLocalMap,也就是一个Map,它的键是threadLocal,值为就是变量的副本。通过ThreadLocal的get()方法可以获取该线程变量的本地副本,在get方法之前要先set,否则就要重写initialValue()方法。ThreadLocal 不是用于解决共享变量的问题的,不是为了协调线程同步而存在,而是为了方便每个线程处理自己的状态而引入的一个机制.
ThreadLocal的使用场景:
数据库连接:在多线程中,如果使用懒汉式的单例模式创建Connection对象,由于该对象是共享的,那么必须要使用同步方法保证线程安全,这样当一个线程在连接数据库时,那么另外一个线程只能等待。这样就造成性能降低。如果改为哪里要连接数据库就来进行连接,那么就会频繁的对数据库进行连接,性能还是不高。这时使用ThreadLocal就可以既可以保证线程安全又可以让性能不会太低。但是ThreadLocal的缺点时占用了较多的空间。