关键作用是使变量在多个线程之间可见

  1. public class VolativeText {
  2. public static void main(String[] args) throws InterruptedException {
  3. Student student=new Student();
  4. new Thread(new Runnable() {
  5. @Override
  6. public void run() {
  7. student.GetMethon();
  8. }
  9. }).start();
  10. Thread.sleep(1000);//睡眠之后修改布尔值
  11. student.GetName(false);//改变布尔值以结束程序运行
  12. }
  13. static class Student
  14. {
  15. public boolean flag=true;
  16. public Student GetName(boolean flag)
  17. {
  18. this.flag=flag;
  19. return this;
  20. }
  21. public void GetMethon()
  22. {
  23. System.out.println("开始");
  24. while (flag){//死循环
  25. }
  26. System.out.println("结束");
  27. }
  28. }
  29. }

image-20210310223438566

程序并没有因为我修改之后结束运行,因为线程对共享变量具有不可见性,main线程修改布尔值之后,子线程看不到值的修改。因此要想实现线程的可见性这里可以加上volative关键字修饰公共变量

volative关键字的作用:使线程在强制公共内存中读取变量值,保证可见性

  1. public class Text10 extends Thread {
  2. private volatile static int count;
  3. @Override
  4. public void run() {
  5. Addcount();
  6. }
  7. public static void Addcount()
  8. {
  9. for (int i = 0; i < 1000; i++) {
  10. count++;
  11. }
  12. System.out.println(Thread.currentThread().getName()+"-->"+count);
  13. }
  14. public static void main(String[] args) {
  15. for (int i = 0; i < 10; i++) {
  16. Text10 text10=new Text10();
  17. text10.start();
  18. }
  19. }
  20. }

image-20210310232145063

按道理输出1000的整数倍数才对,但是变量在自增的过程中没有更新到又被读取再修改,因此volatile不具备原子性,正确办法将方法加上synchronized关键字

image-20210310232349272

  • volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,volatile只能修饰变量,而synchronized可以修饰方法代码块,在开发中使用synchronized比例还是挺大的。

  • 多线程访问volatile变量不会发生阻塞,而synchronized可能会阻塞。

  • volatile能保证数据的可见性,但是不能保证原子性,而synchronized可以保证原子性,也可以保证可见性,因为

  • synchronized会将线程的工作内存和主内存进行同步

  • volatile关键字保证多个线程之间的可见性,synchronized关键字解决线程访问公共资源的同步性。

    区别 synchronized volatile
    使用上 只能用于修饰方法、代码块 只能修饰实例变量或者类关键字
    原子性保证 能保证,锁可以保护数据不被打断 无法保证
    可见性保证 能保证,排它方式使同步代码串行 能保证,可以读取公共变量
    有序性保证 能保证,在同步串行的时候 能保证,禁止JVM以及处理器进行排序
    阻塞情况 会发生阻塞 不会发生阻塞

i++不是原子操作,除了使用synchronized进行同步,也可以使用AtomicInteger/AtomicLong进行实现

  1. import java.util.concurrent.atomic.AtomicInteger;
  2. public class Text10 extends Thread {
  3. private static AtomicInteger count=new AtomicInteger();
  4. @Override
  5. public void run() {
  6. AddCount();
  7. }
  8. public static void AddCount()
  9. {
  10. for (int i = 0; i < 1000; i++) {
  11. count.getAndIncrement();//相当于i++
  12. //count.incrementAndGet();//相当于++i
  13. }
  14. System.out.println(Thread.currentThread().getName()+"-->"+count.get());
  15. }
  16. public static void main(String[] args) {
  17. for (int i = 0; i < 10; i++) {
  18. Text10 text10=new Text10();
  19. text10.start();
  20. }
  21. }
  22. }

image-20210310234739741

CAS(Compare And Swap)是由硬件实现的,

CAS可以将read(读)-modify(修改)-write(写)转化为原子操作

i++自增过程:

从主内存调取i变量值

对i值加1

再把加1过后的值保存到主内存

CAS原理:在把数据更新到主内存时,再次读取主内存变量的值,如果现在变量的值与期望的值一样就更新。

image-20210311220552964

  1. public class CASText {
  2. public static void main(String[] args) {
  3. CASControl casControl=new CASControl();
  4. for (int i = 0; i <10000 ; i++) {
  5. new Thread(new Runnable() {
  6. @Override
  7. public void run() {
  8. System.out.println(Thread.currentThread().getName()+"==>"+casControl.incrementAndGet());
  9. }
  10. }).start();
  11. }
  12. }
  13. }
  14. class CASControl
  15. {
  16. volatile static long value;//使用volatile修饰线程可见性
  17. public long getValue()
  18. {
  19. return value;
  20. }
  21. private boolean Expect(long oldValue,long newValue)
  22. {
  23. synchronized (this)
  24. {
  25. if(value==oldValue)
  26. {
  27. value=newValue;
  28. return true;
  29. }
  30. else
  31. return false;
  32. }
  33. }
  34. public long incrementAndGet()
  35. {
  36. long oldvalue;
  37. long newValue;
  38. do {
  39. oldvalue=value;
  40. newValue=oldvalue+1;
  41. }while (!Expect(oldvalue, newValue));
  42. return newValue;
  43. }
  44. }

image-20210311223403577

CAS实现原子操作背后有一个假设:共享变量当前值与当前线程提供的期望值相同,就认为变量没有被其他线程过。

实际上这种假设不一定成立,假如count=0

A线程对count值修改为10

B线程对count值修改为30

C线程对count值修改为0

当前线程看到count=0,不能认为count没有被其他线程更新,这种结果是否能被接受?

这就是CAS中的ABS问题即共享变量经过了A=》B=》A的更改

是否能够接受ABA问题跟算法实现有关

如果想要规避ABA问题,可以为共享变量引入一个修订号,或者时间戳,每次修改共享变量值,相应的修订号加1.,就会变更为[A,0]=>[B,1]=>[A,2],每次对共享变量的修改都会导致共享变量的增加,通过这个标识就可以判断。AtomicStampedReference类就是基于这个思想产生的。

原子类变量是基于CAS实现的,当对共享变量进行read(读)-modify(修改)-write(写)操作时,通过原子类变量可以保障原子性与可见性,对变量的read(读)-modify(修改)-write(写)操作不是指一个简单的赋值,而是变量的新值,依赖变量的旧值,如自增操作i++,由于volatile只能保证原子的可见性,而不能保证原子性,原变量类内部就是一个借助volatile变量,并且保障了该变量的read-modify-wirte操作的原子性,有时把原子变量看作一个增强的volatile变量,原子变量类有12个

分组 原子变量类
基础数据型 AtomicInteger、AtomicLong、AtomicBoolean
数组型 AtomicIntegerArry、AtomicLongArry、AtomicReferenceArry
字段更新器 AtomocIntegerFiledUpdater、AtomicLongFieldUpdate、AtomicReferenceFiledUpdater
引用型 AtomicReference、AtomicStampedReference、AtomicMarkableReference
  1. import java.util.Random;
  2. import java.util.concurrent.atomic.AtomicLong;
  3. public class Text15 {
  4. //构造方法私有化
  5. private Text15(){}
  6. //私有静态对象
  7. private static final Text15 text=new Text15();
  8. //公共静态方法返回该类的实例
  9. public static Text15 getInstance()
  10. {
  11. return text;
  12. }
  13. //使用原子类记录保存请求总数 成功数 失败数
  14. private final AtomicLong RequestCount=new AtomicLong(0);
  15. private final AtomicLong SuccessCount=new AtomicLong(0);
  16. private final AtomicLong FailCount=new AtomicLong(0);
  17. //进行自增
  18. public void RequestCount()
  19. {
  20. RequestCount.incrementAndGet();
  21. }
  22. public void SuccessCount()
  23. {
  24. SuccessCount.incrementAndGet();
  25. }
  26. public void FailCount()
  27. {
  28. FailCount.incrementAndGet();
  29. }
  30. //查看总数
  31. public long GetRequestCount()
  32. {
  33. return RequestCount.get();
  34. }
  35. public long GetSuccessCount()
  36. {
  37. return SuccessCount.get();
  38. }
  39. public long GetFailCount()
  40. {
  41. return FailCount.get();
  42. }
  43. }
  44. class Text16
  45. {
  46. public static void main(String[] args) throws InterruptedException {
  47. new Thread(new Runnable() {
  48. @Override
  49. public void run() {
  50. for (int i = 0; i <1000 ; i++) {
  51. Text15.getInstance().RequestCount();//请求数量
  52. int num=new Random().nextInt();
  53. if(num%2==0)//如果是偶数就成功
  54. {
  55. Text15.getInstance().SuccessCount();
  56. }
  57. else
  58. Text15.getInstance().FailCount();
  59. }
  60. }
  61. }).start();
  62. Thread.sleep(1000);
  63. System.out.println("请求总数:"+Text15.getInstance().GetRequestCount());
  64. System.out.println("请求成功"+Text15.getInstance().GetSuccessCount());
  65. System.out.println("请求失败"+Text15.getInstance().GetFailCount());
  66. }
  67. }

image-20210315213254758

版权声明:本文为cg-ww原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/cg-ww/p/14540450.html