JAVA安全之原子操作

可见性和原子性是导致线程安全问题的 主要原因

原子操作
原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,
也不可以被切割而只执行其中的一部分(不可中断性)
将整个操作视作一个整体,资源在该次操作中保持一致,这是原子性的核心特征。

实现原子操作的几种方式:
–synchronized

package day05cas;

public class Cas01 {
	volatile  int i=0;
	/**
	 * 实现原子性 最快的方式是添加一个 synchronized关键字
	 * 或者用ReentrantLock来加锁 解锁,但是并发度很低  Lock look =new ReentrantLock();
	 * AtomicInteger 原子性修饰
	 */
	public synchronized void add() {
		i++;
	}
}

–ReentrantLock

package day05cas;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Cas02 {
	volatile  int i=0;
	/**
	 * 实现原子性 最快的方式是添加一个 synchronized关键字
	 * 或者用ReentrantLock来加锁 解锁,但是并发度很低  Lock lock =new ReentrantLock();
	 * AtomicInteger 原子性修饰
	 */
	Lock lock =new ReentrantLock();
	public  void add() {
		lock.lock();
		i++;
		lock.unlock();
	}
}

–AtomicInteger

package day05cas;

import java.util.concurrent.atomic.AtomicInteger;

public class Cas03 {
	/**
	 * 实现原子性 最快的方式是添加一个 synchronized关键字
	 * 或者用ReentrantLock来加锁 解锁,但是并发度很低  Lock look =new ReentrantLock();
	 * AtomicInteger 原子性修饰
	 *原子操作类
	 */
	AtomicInteger i=new AtomicInteger(0);
	public  void add() {
		i.incrementAndGet();
	}
}

–JVM提供接口Unsafe

package day05cas;

import java.lang.reflect.Field;

import sun.misc.Unsafe;
//JVM提供接口Unsafe
public class Cas04 {
	volatile  int i=0;
	private static Unsafe unsafe=null;
	//偏移量
	private static long valueOffset;
	
	//Exception in thread "main" java.lang.ExceptionInInitializerError
	/*
	 * JDK自身可用,禁止应用调用 可用反射操作使用
	 * static { unsafe=Unsafe.getUnsafe(); }
	 */
	
	static {
		try {
			Field field =Unsafe.class.getDeclaredField("theUnsafe");
			field.setAccessible(true);
			unsafe = (Unsafe)field.get(null);
			Field iFied = Cas04.class.getDeclaredField("i");//i的偏移量
			valueOffset =unsafe.objectFieldOffset(iFied);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		}
	
	public synchronized void add() {
		for(;;) {//防止失败
			//1、拿旧值
			int current = unsafe.getIntVolatile(this, valueOffset);
			//通过cas操作来修改i的值
			if(unsafe.compareAndSwapInt(this, valueOffset, current, current+1)) {
				break;
			}
		}
	}
	public static void main(String[] args) {
		
	}
}

测试代码:

package day05cas;

public class Demo01 {

//使用synchronized ,lock 会有线程阻塞 cas没有	  AtomicInteger 用cas实现
	public static void main(String[] args) throws InterruptedException {
		// TODO Auto-generated method stub
		final Cas04 cas =new Cas04();
		for(int i=0;i<6;i++) {
			new Thread(
					new Runnable() {

						@Override
						public void run() {
							// TODO Auto-generated method stub
							for(int j=0;j<10000;j++) {
								cas.add();
							}
							System.out.println("done...");
						}
						
					}).start();
		}
		Thread.sleep(6000L);
		System.out.println(cas.i);
	}

}

CAS(Compare and swap)
Compare and swap 比较和交换。属于硬件的同步原语,处理器提供了基本内存操作的原子性保证。
CAS操作需要输入两个数值,一个旧值A(期望操作前的值) 和一个新值B,在操作期间先对旧值进行比较,
若没有发生变化,才交换成新值,发生了变化则不交换。

JAVA中的sun.misc.Unsafe类,提供了compareAndSwapInt()和compareAndSwapLong()等几个方法实现CAS

CAS只能对单值操作
JDK随处可用

J.U.C包内的原子操作封装类
AtomicBoolean:原子更新布尔类型
AtomicInteger:原子更新整型
AtomicLong:原子更新长整型

AtomicIntegerArray:原子更新整型数组里的元素(底层存的时普通int数组)
AtomicLongArray:原子更新长整型数组里的元素
AtomicReferenceArray:原子更新引用类型数组里的元素

AtomicIntegerFieldUpdater:原子更新整型的字段的更新器
AtomicLongFieldUpdater:原子更新长整型字段的更新器
AtomicReferenceFiledUpdater:原子更新引用类型里的字段

AtomicReference:原子更新引用类型(修改的是引用指向,不是对象)
AtomicStampedReference:原子更新带有版本号的引用类型
AtomicMarkableReference:原子更新带有标记位的引用类型

AtomicIntegerFieldUpdater:

package atomic;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class Demo_AtomicIntegerFieldUpdater {
	//新建AtomicIntegerFieldUpdater对象,需要指明是哪个类中的哪个字段
	
	private static AtomicIntegerFieldUpdater<User> atom=
			AtomicIntegerFieldUpdater.newUpdater(User.class, "id");
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		User user =new User(100,100,"Kody");
		atom.addAndGet(user, 50);
		System.out.println("addAndGet(user,50)     调用后值变为:" + user.toString());
	}
}
class User{
//使用AtomicIntegerFieldUpdater 修改必须用volatile修饰
	volatile int id;
	volatile int age;
	
	private String name;
	
	public User(int id,int age,String name) {
		this.id=id;
		this.age=age;
		this.name=name;
	}
	public String toString() {
		return " id="+id+" age="+age+" name="+name;
	}
}

JDK1.8更新
计数器增强,高并发下性能更好
更新器:DoubleAccumulator、LongAccumualtor
计数器:DoubleAdder、LongAdder

LongAdder:

package com.study.wyy.day01;

import java.util.concurrent.atomic.LongAdder;

public class Demo01 {
    //并发情况使用 不能非常精确
    public static void main(String[] args) throws InterruptedException {
    		// TODO Auto-generated method stub
        LongAdder adder =new LongAdder();
        for(int i=0;i<6;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Long starttime=System.currentTimeMillis();
                    while(System.currentTimeMillis()-starttime<2000){//运行两秒
                        adder.increment();
                    }
                    long endTime=System.currentTimeMillis();
                }
            }).start();
        }
        Thread.sleep(3000L);
        System.out.println(adder.sum());
    }
}

LongAccumulator:

package com.study.wyy.day01;

import java.util.concurrent.atomic.LongAccumulator;

public class Demo02 {
    public static void main(String[] args) {
    		// 自定义 返回规则
        LongAccumulator accumulator=new LongAccumulator(
                (x,y)->{
                    System.out.println("x="+x+",y="+y);
                    return x+y;
                }
        ,0L);
        for(int i=0;i<3;i++){
            accumulator.accumulate(1);
        }
        System.out.println("result="+accumulator.get());
    }
}

原理:分成多个操作单元,不同线程更新不同的单元
只有需要汇总的时候才计算所有单元的操作
场景:高并发频繁更新、不太频繁读取

CAS的三个问题

1、循环+CAS,自旋的实现让所有线程都处于高频运行,争抢CPU执行时间的状态。(持续runable状态)
如果长时间 不成功,会带来很大的CPU资源损耗
2、仅针对单个变量的操作,不能用于多个变量来实现原子操作。
3、ABA问题(版本问题)

thread1、thread2 同时读取到i=0;后
thread1、thread2都要执行CAS(0,1)操作,
假设thread2操作稍后于thread1,则thread1执行成功
thread1 紧接着执行了CAS(1,0),将i的值改回0

ABA问题示例:

package com.study.wyy.day01.aba;

// 存储在栈里面元素 -- 对象
public class Node {
    public final String value;
    public Node next;

    public Node(String value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "value=" + value;
    }
}
package com.study.wyy.day01.aba;

import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;

//实现一个栈 (后进先出)
public class Stack {
    //top cas 无锁修改
    AtomicReference<Node> top =new AtomicReference<Node>();
    public void push(Node node){//入栈
        Node oldTop;
        do{
            oldTop=top.get();
            node.next=oldTop;
        }while (!top.compareAndSet(oldTop,node));//CAS替换栈顶
    }
    //出栈 -- 取出栈顶,为了演示ABA效果,增加一个CAS操作的延时
    public Node pop(int time){
        Node newTop;
        Node oldTop;
        do{
            oldTop=top.get();
            if(oldTop ==null){//如果没有值 ,就返回null
                return  null;
            }
            newTop =oldTop.next;
            if(time !=0){
                LockSupport.parkNanos(1000*1000*time);//休眠指定的时间
            }
        }while (!top.compareAndSet(oldTop,newTop));//将下一个节点设置为top
        return oldTop;//将旧的top作为值返回
    }
}

package com.study.wyy.day01.aba;

import java.util.concurrent.atomic.AtomicStampedReference;
import java.util.concurrent.locks.LockSupport;

public class ConcurrentStack {
    //top cas无锁修改
    //AtomicReference<Node> top =new AtomicReference<Node>();
    AtomicStampedReference<Node> top =new AtomicStampedReference<Node>(null,0);

    public void push(Node node){//入栈
        Node oldTop;
        int v;
        do{
            v=top.getStamp();
            oldTop=top.getReference();
            node.next =oldTop;
        }while (!top.compareAndSet(oldTop,node,v,v+1));
    }
    //出栈 -- 取出栈顶,为了演示ABA效果,增加一个CAS操作的延时
    public Node pop(int time ){
        Node newTop;
        Node oldTop;
        int v;
        do{
            v=top.getStamp();
            oldTop = top.getReference();
            if(oldTop ==null){//如果没有值,就返回null
                return  null;
            }
            newTop =oldTop.next;
            if(time !=0){//模拟延时
                LockSupport.parkNanos(1000*1000*time);//休眠指定的时间
            }
        }while (!top.compareAndSet(oldTop,newTop,v,v+1));//将下一个节点设置为top
        return  oldTop;//将旧的top作为值返回
    }
}

package com.study.wyy.day01.aba;

import java.util.concurrent.locks.LockSupport;

public class Test {
    public static void main(String[] args) {
    		// TODO Auto-generated method stub
        //ConcurrentStack stack=new ConcurrentStack();
        Stack stack=new Stack();
        stack.push(new Node("B"));//B入栈
        stack.push(new Node("A"));//A入栈

        Thread thread1=new Thread(()->{
            Node node =stack.pop(800);
            System.out.println(Thread.currentThread().getName() +" "+node.toString());
            System.out.println("done1.....");
        });
        thread1.start();
        Thread thread2 = new Thread(()->{
            LockSupport.parkNanos(1000*1000*300L);
            Node nodeA= stack.pop(0);//取出A
            System.out.println(Thread.currentThread().getName() +" "+nodeA.toString());

            Node nodeB = stack.pop(0);//取出B,之后B处于游离状态
            System.out.println(Thread.currentThread().getName() +" "+nodeB.toString());

            stack.push(new Node("D"));//D入栈
            stack.push(new Node("C"));//C入栈
            stack.push(nodeA);//A入栈
            System.out.println("done2...");
        });
        thread2.start();
        LockSupport.parkNanos(1000*1000*1000*2L);

        System.out.println("开始遍历Stack:");
        Node node =null;
        while ((node =stack.pop(0))!=null){
            System.out.println(node.value);
        }
    }
}

线程安全概念(可见性 原子性)
竟态条件;如果程序 运行顺序的改变会影响最终结果,就说存在竟态条件。
大多数竟态条件的本质,就是基于某种可能失效的观察结果来做出判断或执行某个计算
临界区:存在静态条件的代码区域叫临界区

资源竞争(资源 共享内存区域(堆内存,方法区),DB/redis之类) 共享资源

共享资源
只有当多个线程更新共享资源时,才会发生竟态条件,可能会出现线程安全问题。

栈封闭时,不会在线程之间共享的变量,都是线程安全的。
局部对象 引用本身不共享,但是引用的对象存储在共享堆中。如果方法内创建的对象,只在方法中传递,
并且不对其他线程可用,那么也是线程安全的。
不可变的共享对象 来保证在线程间共享时不会被修改,从而实现线程安全。
实例被创建,value变量就不能再被修改,这就是不可变性。
使用ThreadLocal时,相当于不同的线程操作的是不同的资源,所以不存在线程安全问题

重点:
i++这个操作不是原子操作
原子操作的概念
CAS机制的概念,利用CAS实现原子性的数字变更
AtomicInteger等类底层就是利用CAS机制实现
JDK提出了高并发场景性能更好的累加计数器
带有版本号的数字引用类型,可以实现版本号锁
线程安全相关的概念

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值