概述
对于并发控制来说,锁是一种悲观的策略。它总是假设每一次的临界区操作会产生冲突,因此,必须对每次操作都小心翼翼。如果有多个线程同时访问临界区资源,就宁可牺牲性能让线程进行等待,所以说锁会阻塞线程执行。 而无锁采用的是一种乐观的策略,它会假设对资源的访问是没有冲突的,既然没有冲突,所以不用等待。遇到冲突,无锁采用的策略是一种叫做CAS的技术来鉴别线程冲突。
CAS
CAS全称为compile and swap,它包含三个参数(V,E,N)。V表示要跟新的变量,E表示预期值,N表示新值。仅当V值等于E值的时候,才会将V的值设为N,如果V值和E值不同,则说明有其他线程做了跟新,则当前线程什么都不做。
原子变量
无锁的线程安全整数:AtomicInteger
就具体实现来说,Atomic中保存了一个核心字段
private volatile int value;
这里以具体demo的形式来给出用法,下面的几个原子变量一致。
package nolock;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerDemo {
static AtomicInteger i = new AtomicInteger();
public static class AddThread implements Runnable{
public void run(){
for(int k = 0; k < 10000; k++){
i.incrementAndGet();
}
}
}
public static void main(String[] args) throws InterruptedException{
Thread[] ts = new Thread[10];
for(int k = 0;k < 10; k++){
ts[k] = new Thread(new AddThread());
}
for(int k = 0; k < 10; k++)
ts[k].start();
for(int k = 0; k < 10; k++)
ts[k].join();
System.out.println(i);
}
}
运行结果:
100000
无锁的对象引用:AtomicReference
同理他是对对象的引用,但是这里会有一个ABA问题。
如果A要访问的对象值初始值为a,后来线程B改为b,再后来线程C改为a,此时就产生了脏读了。所以但用这个AtomicReference解决不了问题,下面用这个例子来说明问题。
package nolock;
import java.util.concurrent.atomic.AtomicReference;
public class AtmoicReferenceDemo {
static AtomicReference money = new AtomicReference();
public static void main(String[] args){
money.set(19);
for(int i = 0; i < 3; i++){
new Thread(){
public void run(){
while(true){
while(true){
Integer m = money.get();
if(m < 20){
if(money.compareAndSet(m,m+20)){
System.out.println("余额小于20元,充值成功,余额:" + money.get() + "元");
break;
}
}else{
//无需充值
break;
}
}
}
}
}.start();
new Thread(){
public void run(){
for(int i = 0; i < 100; i++){
while (true){
Integer m = money.get();
if(m > 10){
System.out.println("大于10元");
if(money.compareAndSet(m,m-10)){
System.out.println("成功消费10元,余额:" + money.get());
break;
}
}else{
System.out.println("没有足够的金额");
break;
}
}
try{
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}.start();
}
}
}
这个例子的结果会无限循环下去,余额值会一直浮动变化。
那么用什么来解决呢?
加一个时间戳stamp
AtomicStampedReference
带有时间戳的对象引用:AtomicStampedReference
package nolock;
import java.util.concurrent.atomic.AtomicStampedReference;
public class AtomicStampedReferenceDemo {
static AtomicStampedReference money = new AtomicStampedReference(19,0);
public static void main(String[] args){
//模拟多个线程同时更新后台数据库,为用户充值
for(int i = 0; i < 3; i++){
final int timestamp = money.getStamp();
new Thread(){
public void run(){
while(true){
while(true){
Integer m = money.getReference();
if(m < 20){
if(money.compareAndSet(m,m+20,timestamp,timestamp+1)){
System.out.println("余额小于20元,充值成功,余额:" + money.getReference() + "元");
break;
}
}else{
//无需充值
break;
}
}
}
}
}.start();
}
//用户线程,模拟消费
new Thread(){
public void run(){
for(int i = 0; i < 100; i++){
while(true){
int timestamp = money.getStamp();
Integer m = money.getReference();
if(m > 10){
System.out.println("大于10元");
if(money.compareAndSet(m,m-10,timestamp,timestamp+1)){
System.out.println("成功消费10元,余额:" + money.getReference());
break;
}
}else{
System.out.println("没有足够余额");
break;
}
}
try{
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}.start();
}
}
用这个方法余额就只会赠予一次。
数组也能无锁:AtomicIntegerArray
package nolock;
import java.util.concurrent.atomic.AtomicIntegerArray;
public class AtomicIntegerArrayDemo {
static AtomicIntegerArray arr = new AtomicIntegerArray(10);
public static class AddThread implements Runnable{
public void run(){
for(int k = 0;k < 10000;k++){
arr.getAndIncrement(k % arr.length());
}
}
}
public static void main(String[] args) throws InterruptedException{
Thread[] ts = new Thread[10];
for(int k = 0;k < 10; k++){
ts[k] = new Thread(new AddThread());
}
for(int k = 0;k < 10; k++)
ts[k].start();
for(int k = 0;k < 10; k++)
ts[k].join();
System.out.println(arr);
}
}
让普通变量也享受原子操作:AtomicIntegerFieldUpdater
package nolock;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class AtomicIntegerFieldUpdaterDemo {
public static class Candidate{
int id;
volatile int score;//必须为volatile,并且不能private,也不能用static修饰
}
public final static AtomicIntegerFieldUpdater scoreUpdater
= AtomicIntegerFieldUpdater.newUpdater(Candidate.class,"score");
public static AtomicInteger allScore = new AtomicInteger(0);
public static void main(String[] args)throws InterruptedException{
final Candidate stu = new Candidate();
Thread[] t = new Thread[10000];
for(int i = 0; i < 10000; i++){
t[i] = new Thread(){
public void run(){
if(Math.random() > 0.4){
scoreUpdater.incrementAndGet(stu);
allScore.incrementAndGet();
}
}
};
t[i].start();
}
for(int i = 0; i < 10000; i++){
t[i].join();
}
System.out.println("score = "+ stu.score);
System.out.println("allScore = " + allScore);
}
}
因为AtomicIntegerFieldUpdater保证了Candidate.score的线程安全,所以Candidate.score的值总是和allscore的值相等。
AtomicIntegerFieldUpdater的使用注意事项:
1.必须为volatile
2.不能private(因为Updater是使用反射得到这个变量的,如果变量不可见就会出错)
3.不能用static修饰(因为Unsafe.objectFieldOffset()不支持静态变量)
参考文献:《Java高并发程序设计》