目录
volatile实现可见性
volatile关键字的作用使变量在多个线程之间可见,满足可见性。
volatile 的作用可以
强制线程从公共内存中读取变量的值
,而不是从工作内存中读取
![](https://img-blog.csdnimg.cn/20210201143306614.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyODkyODU4,size_16,color_FFFFFF,t_70)
案例:下列代码在某些电脑会存在数据可风性问题,导致while()一直循环。(但是在while()有语名就不会一直执行)
public class Test02 {
public static void main(String[] args) {
//创建 PrintString 对象
PrintString printString = new PrintString();
//开启子线程,让子线程执行 printString 对象的
// printStringMethod()方法
new Thread(new Runnable() { @Override public void run() { printString.printStringMethod(); } }).start();
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }//main 线程睡眠 1000 毫秒
System.out.println("在 main 线程中修改打印标志");
printString.setContinuePrint(false); //程序运行,查看在 main 线程中修改了打印标志之后 ,子线程打印是否可以结束打 印 //程序运行后, 可能会出现死循环情况 //分析原因: main 线程修改了 printString 对象的打印标志后, 子线程读不到 //解决办法: 使用 volatile 关键字修饰 printString 对象的打印标志. // volatile 的作用可以强制线程从公共内存中读取变量的值,而不是从工作内 存中读取
}
static class PrintString {
private boolean continuePrint = true;
public PrintString setContinuePrint(boolean continuePrint) {
this.continuePrint = continuePrint;
return this;
}
public void printStringMethod() {
System.out.println(Thread.currentThread().getName() + "开始....");
while (continuePrint) {
}
System.out.println(Thread.currentThread().getName() + "结束++++++++++++++");
}
}
}
volatile与synchronized比较
- volatile性能优于synchronized,volatile只修饰变量,而synchronized可以修饰方法,代码块
- 多线程访问volatile不会阻塞,而synchronized可能会阻塞(volatile就像一个从内存取值,而synchronized修饰的代码块可能有很多的操作)
- volatile能保证数据的可见性,但是不能保证原子性;而synchronized可以保存原子性与可见性(synchronized含有隐形的刷新数据功能 )
volatile的非原子性
volatile的作用是强制线程从公共内存中读取变量的值,而不是从工作内存中读取。没有关于原子性的功能,只是可见性。
如 a++ 使用分为 1)取出a值 2)计算a+1 3)存放新的a值
volatile只保存了第一步,如果在多线程中可以使用原子类进行自增自减操作
CAS
原理:在把数据更新到主内存时,再次读取主内存变量的值,如果现在变量的值与期望的值(操作起始时读取的值)一样就更新,否则重新执行
如下:在修改value的时候,取出当前的value与 操作起始时读取的值比较,一样时才执行写入
public class CASTest {
public static void main(String[] args) {
CASCounter casCounter = new CASCounter();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
casCounter.incrementAndGet();
}
}).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println(casCounter.getValue());
}
}
class CASCounter{
volatile private long value = 0;
public long getValue() {
return value;
}
//定义 comare and swap方法
private boolean compareAndSwap(long exepectedValue,long newValue){
synchronized (this){
if(value == exepectedValue){
value = newValue;
return true;
}else {
System.err.println("冲突了");
return false;
}
}
}
public long incrementAndGet(){
long oldvalue;
long newValue;
do {
oldvalue = value;
newValue = oldvalue+1;
}while (!compareAndSwap(oldvalue,newValue));
return value;
}
}
ABA问题
如果出现以下情况:
CAS
实现原子操作背后有一个假设“
共享变量的当前值与当前线程提供的期望值相同, 就认为这个变量没有被其他线程修改过
。
实际上这种假设不一定总是成立.
如有共享变量
count = 0
- A 线程对 count 值修改为 10
- B 线程对 count 值修改为 20
- C 线程对 count 值修改为 0
当前线程看到
count
变量的值现在是
0,
现在是否认为
count
变 量的值没有被其他线程更新呢?
这种结果是否能够接受
??
这就是
CAS
中的
ABA
问题
,
即共享变量经历了
A->B->A
的更新。
如果要规避ABA问题,可以为共享变量引入一个修订号(时间戳)来表示。过程为
: [A,0] ->[B,1]->[A,2]。这时虽然共享值没有变化,但是修订号已经发生了改变
实现CAS的原子类
原子变量类基于CAS实现,原子类有12个:如
![](https://img-blog.csdnimg.cn/20210202085350818.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyODkyODU4,size_16,color_FFFFFF,t_70)
例:AtomicLong类
相当于Long的原子类,
get 获取当前值
set 设置当前值
lazySet
getAndSet 先获取当前值,再设置当前值
compareAndSet
weakCompareAndSet
getAndIncrement == i++
getAndDecrement == i--
getAndAdd(n) 先获取当前值 i,再 i += n;
incrementAndGet == ++i
decrementAndGet == --i
addAndGet(n) 先 i+=n 再获取i
getAndUpdate
updateAndGet
getAndAccumulate
accumulateAndGet
toString
intValue
longValue
floatValue
doubleValue
serialVersionUID
unsafe
valueOffset
VM_SUPPORTS_LONG_CAS
value
int i = 0;
AtomicLong atomicLong = new AtomicLong(0);
// atomicLong.addAndGet(1); //+n
//相当于 --i
System.err.println(atomicLong.decrementAndGet());
//相当于 i--
System.err.println(atomicLong.getAndIncrement());
//相当于 i+=10 后返回i
System.err.println(atomicLong.addAndGet(10));
atomicLong.set(0);
System.err.println(atomicLong.addAndGet(10));
System.err.println(atomicLong.getAndSet(10));
例:AtomicIntegerArray类
相当于Integer[] 的源子类
AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
//1)返回指定位置的元素
System.out.println(atomicIntegerArray.get(0)); //返回下标为0的元素
//2)设置指定位置的元素
atomicIntegerArray.set(0,10); //将下标为0的元素设置为10
//在设置数组元素的同时,返回元素的旧值
System.out.println(atomicIntegerArray.getAndSet(0,20));
//3)修改元素的值,把数组元素加上某个值
System.out.println(atomicIntegerArray.getAndSet(0,10));//下标为0元素加10,返回旧值
System.out.println(atomicIntegerArray.addAndGet(0,10));//下标为0元素加10,返回新值
//4)CAS操作
//如果数组中下标为0的元素,值为10,则将其修改为222,返回 boolean
System.out.println(atomicIntegerArray.compareAndSet(0,10,222));
//5)自增/自减操作
System.out.println(atomicIntegerArray.decrementAndGet(0)); //相当于 --i[0]
System.out.println(atomicIntegerArray.incrementAndGet(0)); //相当于 ++i[0]
public class Test02 {
static AtomicIntegerArray integerArray = new AtomicIntegerArray(10);
public static void main(String[] args) {
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new th();
}
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(integerArray);
}
static class th extends Thread{
@Override
public void run() {
for (int i = 0; i< 1000; i++){
for (int j = 0 ; j < 10; j++){
integerArray.incrementAndGet(j);
}
}
}
}
}
例:AtomicIntegerFieldUpdater类
可以对原子整数字段进行更新(对一个对象的整数属性更新),要求:
1)字符必须使用volatile修饰,使线程之间可见
2)只能是实例变量,不能是静态变量,也不能使用final修饰
public class AtomicIntegerFieldUpdaterTest {
public static void main(String[] args) {
User user = new User();
AtomicIntegerFieldUpdater<User> updater = AtomicIntegerFieldUpdater.newUpdater(User.class,"age"); //age是一个用volatile修饰的整数字段
System.out.println(updater.addAndGet(user,1)); //age属性直接操作是不能保证原子性的
}
}
class User{
volatile int age;
String name;
}
例:AtomicReference类
可以原子读一个对象
static AtomicReference<String> atomicReference = new AtomicReference<>("abc");
public static void main(String[] args) {
for (int i = 0;i < 100; i++){
new Thread(new Runnable() {
@Override
public void run() {
if(atomicReference.compareAndSet("abc","def")){
System.out.println(Thread.currentThread().getName()+"把字符串abc改为def");
}
}
}).start();
}
for (int i = 0;i < 100; i++){
new Thread(new Runnable() {
@Override
public void run() {
if(atomicReference.compareAndSet("def","abc")){
System.out.println(Thread.currentThread().getName()+"还原字符串");
}
}
}).start();
}
}
例:AtomicStampedReference
和AtomicReference类似,它通过版本号解决了ABA问题
static AtomicStampedReference<String> stampedReference = new AtomicStampedReference<>("abc",0);
public static void main(String[] args) {
//获取当前版本号
int j = stampedReference.getStamp();
//如果当前值是abc且版本号为j,则将其值修改为cge,版本号j+1
stampedReference.compareAndSet("abc","cge",j,j+1);