线程安全性
可以在多个线程中调用,并且在线程之间不会出现错误的交互。
原子性
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
i++ 和 ++i就不是原子性。
++i 读取值,将值加1,将值写入i."读取,修改,写入"过程。
竞态条件
- 先检查后执行:
当判断某个条件为真的时候,根据判断结果做出相应的动作。
但判断完成后和执行期间,观察结果有可能会发生变化(判断条件变成假的了)。
复合操作
-
“检查再运行”和“读-改-写” 操作称为复合操作。
-
自增操作可以使用: java.util.concurrent中提供的atomic原子包
atomic原子包
public class AtomicDemo
extends Thread {
private static final AtomicInteger TEST_INT = new AtomicInteger();
@Override
public void run() {
TEST_INT.incrementAndGet();
}
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
AtomicDemo demo = new AtomicDemo();
demo.start();
try {
demo.join();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("最终结果:"+TEST_INT);
}
}
运行结果:
最终结果:1000
多个线程对AtomicInteger类型的变量进行自增操作,运算结果无误。若用普通的int变量,i++多线程操作可能导致结果有误。
源码分析:
- incrementAndGet函数
/**
* Atomically increments by one the current value.
* @return the updated value
*/
public final int incrementAndGet() {
for (;;) {
//获取当前值value
int current = get();
int next = current + 1;
//循环执行到递增成功
if (compareAndSet(current, next))
return next;
}
}
private volatile int value;
value是volatile类型,确保此线程能获取到最新值。
方法不断获取value值再进行递增操作,直至操作成功。
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
compareAndSet使用Unsafe调用native本地方法CAS递增数值。
CAS(Compare and Swap)简要
CAS全称 Compare And Swap(比较与交换),是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。java.util.concurrent包中的原子类就是通过CAS来实现了乐观锁。
CAS利用CPU调用底层指令实现。
单一处理器,进行简单的读写操作时,能保证自身读取的原子性,多处理器或复杂的内存操作时,CAS采用总线加锁或缓存加锁方式保证原子性。
- 1.总线加锁
如i=0初始化,多处理器多线程环境下进行i++操作下,处理器A和B同时读取i值到各自缓存,分别进行递增,回写值i=1相同。处理器提供LOCK#信号,进行总线加锁后,处理器A读取i值并递增,处理器B被阻塞不能读取i值。 - 2.缓存加锁
总线加锁,在LOCK#信号下,其他线程无法操作内存,性能较差,缓存加锁能较好处理该问题。
缓存加锁,处理器A和B同时读取i值到缓存,处理器A提前完成递增,数据立即回写到主内存,并让处理器B缓存该数据失效,处理器B需重新读取i值。
拓展:
https://en.wikipedia.org/wiki/Compare-and-swap
锁
为了保护状态的一致性,要在单一的原子操作中更新相互关联的状态变量。
内置锁
每个Java对象都可以用作一个实现同步的锁,这些锁被称为内置锁(Intrinsic Lock)或监视器锁(Monitor Lock)
synchronized
方法锁:就是该方法所在对象的本身。
静态synchronized方法是从Class对象中获取锁。
添加锁后线程安全,但是性能令人无法忍受。
synchronized锁有可重进入的特性
可重入的概念:
当一个对象得到锁后,再次请求对象锁的时候是可以再次得到对象的锁的。
当一个线程请求其它的线程已经占有的锁时,请求线程将被阻塞。然而内部锁是可重进入的,因此线程在试图获得它自己占用的锁是,请求会成功。重进入意味着请求是基于“每一个线程”,而不是基于“每一次调用”(互斥锁是基于每次调用的)。重进入的实现是通过为每一个锁关联一个请求技术器和一个占有他的线程。当计数为0时,认为锁是未被占用的。线程请求一个未被占有的锁时候,JVM将记录锁的占有者,并且将请求计数设置为1。如果同一个线程再次请求这个锁,计数将递增;每次占用线程退出语句块时,计数器值将递减,直到计数器达到0时候,锁被释放。
http://blog.csdn.net/majian_1987/article/details/49079559
http://blog.csdn.net/aitangyong/article/details/22695399
活跃性与性能
http://www.codeweblog.com/活跃性与性能/
其中一个同步代码块负责保护判断是否只需返回缓存结果的”先检查后执行“操作序列,另一个同步代码块则负责确保对缓存的数值和因数分解结果进行同步更新。
@ThreadSafe
public class CachedFactoriser implements Servlet {
@GuardedBy("this") private BigInteger lastNumber;
@GuardedBy("this") private BigInteger[] lastFactors;
@GuardedBy("this") private long hits;
@GuardedBy("this") private long cacheHits;
public synchronized long getHits() {return hits;}
public synchronized double getCacheHitRatio() {
return (double) cacheHits /(double) hits;
}
public void service(ServletRequest req,ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] ractors = null;
synchronized (this) {
++hits;
if(i.equals(lastNumber)) {
++cacheHits;
factors = lastFactors.clone();
}
}
if(factors == null) {
factors = factor(i);
synchronized (this) {
lastNumber = i;
lastFactors = factors.clone();
}
}
encodeIntoResponse(resp,factors);
}
}