可见性和原子性是导致线程安全问题的 主要原因
原子操作
原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,
也不可以被切割而只执行其中的一部分(不可中断性)
将整个操作视作一个整体,资源在该次操作中保持一致,这是原子性的核心特征。
实现原子操作的几种方式:
–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提出了高并发场景性能更好的累加计数器
带有版本号的数字引用类型,可以实现版本号锁
线程安全相关的概念