Java中Atomic原子类型的详细讲解(一)-刘宇

作者:刘宇
CSDN博客地址:https://blog.csdn.net/liuyu973971883
有部分资料参考,如有侵权,请联系删除。如有不正确的地方,烦请指正,谢谢。

一、什么是原子类型

  • 原子操作是指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何线程上下文切换。
  • Java从JDK 1.5开始提供了java.util.concurrent.atomic包(以下简称Atomic包),这个包中的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。
  • Atomic包大致可以属于4种类型的原子更新方式,分别是
    1. 原子更新基本类型
    2. 原子更新数组
    3. 原子更新引用
    4. 原子更新属性

二、原子类型的实现原理

Atomic包里的类基本都是使用Unsafe实现的包装类,从而达到了原子性的操作。然后通过将内部的value变量用volatile关键字修饰,从而达到了可见性、防止重排序、原子性。

三、利用volatile关键字演示原子性问题

演示

使用三个线程分别对同一个变量进行加1操作,原本理想的结果应该是输出1500个元素,而真实结果却只输出了1498个,原因是有线程输出了同样的结果。是因为volatile关键字可以添加内存屏障有效防止重排序、内存可见性,但是不能确保其原子性,而value+=1时,看似是一行代码,实则是分成了多步执行的。

package com.brycen.concurrency03.atomic;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerTest {
	private static volatile int value = 0;
	private static Set<Integer> set = Collections.synchronizedSet(new HashSet<Integer>());
	
	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				int x= 0;
				while(x<500) {
					int temp = value += 1;
					set.add(temp);
					System.out.println(Thread.currentThread().getName()+":"+temp);
					x++;
				}
			}
			
		});
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				int x= 0;
				while(x<500) {
					int temp = value += 1;
					set.add(temp);
					System.out.println(Thread.currentThread().getName()+":"+temp);
					x++;
				}
			}
			
		});
		Thread t3 = new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				int x= 0;
				while(x<500) {
					int temp = value += 1;
					set.add(temp);
					System.out.println(Thread.currentThread().getName()+":"+temp);
					x++;
				}
			}
			
		});
		t1.start();
		t2.start();
		t3.start();
		t1.join();
		t2.join();
		t3.join();
	}
}

输出结果:
我们可以看到,原本应该输出1500个,但是结果却只输出了1498个。这是因为volatile关键字可以添加内存屏障有效防止重排序、内存可见性,但是不能确保其原子性,而value+=1时,看似是一行代码,实则是分成了多步执行的
在这里插入图片描述

为什么多个线程会输出相同的结果

volatlie关键字没有原子性,那么value+=1这行代码实则会被分为4步执行

  1. 获取value的值
  2. 将获取到的值+1
  3. 将最新值赋值给value
  4. 将value的值刷入内存

假设当时value值为1,当线程1执行完+=操作的第1步时,cpu执行权被线程2抢走,然后线程2执行+=操作,直至输入内存,并输出2,这时cpu执行被线程1抢走,继续执行没有完成的+=操作,那么这时线程1会根据第一步拿到的1进行+1操作,那么返回输出的同样是2。

四、利用AtomicInteger解决原子性问题

package com.brycen.concurrency03.atomic;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerTest {
	private static Set<Integer> set = Collections.synchronizedSet(new HashSet<Integer>());
	private static AtomicInteger value = new AtomicInteger();
    
	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				int x= 0;
				while(x<500) {
					int temp = value.getAndIncrement();
					set.add(temp);
					System.out.println(Thread.currentThread().getName()+":"+temp);
					x++;
				}
			}
			
		});
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				int x= 0;
				while(x<500) {
					int temp = value.getAndIncrement();
					set.add(temp);
					System.out.println(Thread.currentThread().getName()+":"+temp);
					x++;
				}
			}
			
		});
		Thread t3 = new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				int x= 0;
				while(x<500) {
					int temp = value.getAndIncrement();
					set.add(temp);
					System.out.println(Thread.currentThread().getName()+":"+temp);
					x++;
				}
			}
		});
		
		
		t1.start();
		t2.start();
		t3.start();
		t1.join();
		t2.join();
		t3.join();
	}
}

输出结果:
在这里插入图片描述

五、AtomicInteger的基本使用

1、AtomicInteger的创建和get()方法

  • AtomicInteger的创建分为两种:

    1. 无参的,默认值0
    2. 有参的,指定默认值
  • get():用于获取当前值,该方法是不需要锁的,因为他只是去取值

AtomicInteger i = new AtomicInteger();
System.out.println(i.get());//输出0
AtomicInteger j = new AtomicInteger(10)
System.out.println(j.get());//输出10

2、set()

  • 该方法是不需要锁的,因为set方法相当于初始化该变量了
AtomicInteger i = new AtomicInteger();
i.set(12);
System.out.println(i.get());//输出12

3、getAndSet(int)

  • 先取值,再设置值。
AtomicInteger i = new AtomicInteger();
int result = i.getAndSet(10);
System.out.println(result);//输出0
System.out.println(i.get());//输出10

4、getAndAdd(int)

  • 先拿到值并且后加num。
AtomicInteger i = new AtomicInteger(10);
int result = i.getAndAdd(10);
System.out.println(result);//输出10
System.out.println(i.get());//输出20

5、addAndGet(int)

  • 先自动加num,再取值。
AtomicInteger i = new AtomicInteger(10);
int result = i.addAndGet(10);
System.out.println(result);//输出20
System.out.println(i.get());//输出20

6、getAndIncrement()

  • 先拿到值然后再+1
AtomicInteger i = new AtomicInteger();
int result = i.getAndIncrement();
System.out.println(result);//输出0
System.out.println(i.get());//输出1

7、incrementAndGet()

  • 先拿到值然后再+1
AtomicInteger i = new AtomicInteger();
int result = i.incrementAndGet();
System.out.println(result);//输出1
System.out.println(i.get());//输出1

9、getAndDecrement()

  • 先拿到值然后再-1
AtomicInteger i = new AtomicInteger(10);
int result = i.getAndDecrement();
System.out.println(result);//输出10
System.out.println(i.get());//输出9

10、decrementAndGet()

  • 先-1再取值
AtomicInteger i = new AtomicInteger();
int result = i.decrementAndGet();
System.out.println(result);//输出9
System.out.println(i.get());//输出9

11、compareAndSet(int expect, int update)

  • 快速失败策略,是用于判断期望值是否与变量实际值相等,如果相等则将update赋值给变量,否则失败。
//成功案例
AtomicInteger atomicInteger = new AtomicInteger(10);
boolean result = atomicInteger.compareAndSet(10, 12);
System.out.println(result);//输出true
System.out.println(atomicInteger.get());//输出12
//失败案例
AtomicInteger atomicInteger1 = new AtomicInteger(10);
boolean result1 = atomicInteger1.compareAndSet(11, 12);
System.out.println(result1);//输出false
System.out.println(atomicInteger1.get());//输出10

六、AtomicBoolean的基本使用

只有两种值,为0和1,即真或假。

1、AtomicInteger的创建和get()方法

  • AtomicInteger的创建分为两种:

    1. 无参的,默认值0
    2. 有参的,指定默认值
  • get():用于获取当前值,该方法是不需要锁的,因为他只是去取值

AtomicBoolean bool = new AtomicBoolean();
System.out.println(bool.get());//输出false
AtomicBoolean bool = new AtomicBoolean(true);
System.out.println(bool.get());//输出true

2、set()

  • 该方法是不需要锁的,因为set方法相当于初始化该变量了
AtomicBoolean bool = new AtomicBoolean();
bool.set(true);
System.out.println(bool.get());//输出true

3、getAndSet(int)

  • 先取值,再设置值。
AtomicBoolean bool = new AtomicBoolean(true);
boolean result = bool.getAndSet(false);
System.out.println(result);//输出true
System.out.println(bool.get());//输出false

4、compareAndSet(boolean expect, boolean update)

  • 快速失败策略,是用于判断期望值是否与变量实际值相等,如果相等则将update赋值给变量,否则失败。
//成功案例
AtomicBoolean bool = new AtomicBoolean(true);
boolean result = bool.compareAndSet(true, false);
System.out.println(result);//输出true
System.out.println(bool.get());//输出false
//失败案例
AtomicBoolean bool1 = new AtomicBoolean(true);
boolean result1 = bool1.compareAndSet(false, true);
System.out.println(result1);//输出false
System.out.println(bool1.get());//输出true

七、AtomicBoolean的作用

可以当做多线程中的开关flag,从而来代替sychronic这样比较重的锁

案例

  • 如果这个案例中使用基本数据类型的boolean作为标志位,则线程永远不会停止。
package com.brycen.concurrency03.atomic;

import java.util.concurrent.atomic.AtomicBoolean;

public class AtomicBooleanFlag {
	private static AtomicBoolean flag = new AtomicBoolean(true);
	
	public static void main(String[] args) {
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				int i = 0;
				while (flag.get()) {
					i++;
				}
				System.out.println("i="+i);
			}
		}).start();
		
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		flag.set(false);
	}
}

输出

i=128183323

八、AtomicLong的基本使用

他基本上与AtomicInteger的使用方法差不多,这边就不演示了,简单说一下他们的区别

AtomicLong与AtomicInteger的区别

  • AtomicLong比AtomicInteger多了一个VM_SUPPORTS_LONG_CAS的boolean变量,对应的值是VMSupportsCS8(),这个是由JVM调用的。

VMSupportsCS8()方法的作用

  • 因为long类型的数据是64位的,那么在32位CPU中,就需要分高位和低位获取一个long类型的数据,那么就会无法保证原子性的操作。VMSupportsCS8()这个方法就是判断该jvm、cpu是否支持long类型的lockless的CompareAndSet,如果支持,则VMSupportsCS8()方法就不会对数据总线进行加锁了,如果不支持则需要对数据总线进行加锁来保证原子性。

九、compareAndSwap算法(CAS)详解

其实在原子类型中涉及到改变变量数值的操作都会进行CAS算法校验。它是利用Unsafe发送汇编指令达到无锁的状态,其实他是有锁的,只不过他是CPU级别的,不是系统级别的,性能会非常高。它会对比我们的当前值是否和变量值一致,如果一致才会将最新值赋值给变量,否则会快速失败,然后进行循坏尝试。

下面是Integer原子类型中改变数值时的部分源代码,其中compareAndSet中就用到了CAS算法

for(;;){
	int current = get();
	int next = current + delta;
	if (compareAndSet(current, next))
		return current;
}

步骤分析

时序线程1线程2
int current = get();current=1current=1
int next = current + delta;next=2next=2
if (compareAndSet(current, next))释放CPU执行权current==value:判断变量的值value和当前值current是否一致。一致则将要next的值赋值给value
if (compareAndSet(current, next))current==value:判断变量的值value和当前值current是否一致。而线程1中的current值为1,value已经变为2,所以不等,则继续循环,直至相等释放CPU执行权

十、利用compareAndSet方法来解决synchronized会使其他线程block住无法干其他事情的现象

1. 自定义一个异常

package com.brycen.concurrency03.atomic;

public class GetLockException extends Exception {
	
	public GetLockException() {
		super();
	}
	
	public GetLockException(String message) {
		super(message);
	}
}

2. 自定义一个利用compareAndSet算法的锁

package com.brycen.concurrency03.atomic;

import java.util.concurrent.atomic.AtomicInteger;

public class CompareAndSetLock {
	private final AtomicInteger value = new AtomicInteger();
	private Thread currentThread = null;
	public void tryLock() throws GetLockException{
		boolean success = value.compareAndSet(0, 1);
		if (!success) {
//			不相等则抛出异常,表示有其他线程使用
			throw new GetLockException();
		}
		currentThread = Thread.currentThread();
	}
	
	public void unLock() {
		if (0 == value.get()) {
			return;
		}
//		确保解锁的是拿到锁的那个线程
		if (currentThread == Thread.currentThread()) {
			value.compareAndSet(1, 0);
		}
	}
}

3. 自定义一个利用compareAndSet算法的锁

package com.brycen.concurrency03.atomic;

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerDetailTest2 {
	private static CompareAndSetLock lock = new CompareAndSetLock();

	public static void main(String[] args) throws InterruptedException {
		for (int i = 0; i < 5; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					// TODO Auto-generated method stub
					doSomeThing2();
				}
			}).start();
		}
	}
	//这个方法是利用synchronized实现的锁,会使其他线程进入block状态
	private static void doSomeThing() {
		synchronized (AtomicIntegerDetailTest2.class) {
			System.out.println(Thread.currentThread().getName()+": get the lock");
			try {
				Thread.sleep(10000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	private static void doSomeThing2() {
		try {
			lock.tryLock();
			System.out.println(Thread.currentThread().getName()+": get the lock");
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (GetLockException e) {
			// TODO Auto-generated catch block
//			e.printStackTrace();
			System.out.println(Thread.currentThread().getName()+": do other things");
		}finally {
			lock.unLock();
		}
	}
}

输出结果

Thread-0: get the lock
Thread-3: do other things
Thread-4: do other things
Thread-1: do other things
Thread-2: do other things
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值