我们实现一个例子。
我们有一个count变量。建立10个线程,每个线程都对count加1000次。(count++)
public class cas { static int count=0; public static void main(String[] args) throws InterruptedException { Thread[] threads=new Thread[10]; for (int i = 0; i < 10; i++) { Thread thread = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 1000; i++) { count++; } return; } }); thread.start(); threads[i]=thread; } // for(int i=0;i<10;i++) // { // threads[i].join(); // } Thread.sleep(1000);//等所有线程完成计算 System.out.println(count); } }
显而易见 答案并非我们所愿是10000.
这是因为count++这个操作在jvm引擎里执行步骤是:
(1)A=count
(2)B=A+1
(3)count=B
这样有可能有几个线程当拿到count时 还没进行加法 别的线程就已经加完了
(举个例子:
线程小明拿到count 线程小刚拿到count。此时count都为200
CPU对于小明照顾一点,让他先行完成了count++的运算 此时count变为201.
小刚拿来的count副本好好放在自己工作内存 不知道已经变了
所以还是老老实实做到count++将count变为201.此时小刚浪费了自己的一次++的机会。因为白加了)
加了volatile更加离谱。想知道为什么 欢迎看我的博客:【JMM】java内存模型及volatile关键字底层
还有一种方法就是原子类 AtomicInteger不过这次我们就搞了(想不搞的。。。)
public class cas { // static volatile int count=0; static AtomicInteger count=new AtomicInteger(); public static void main(String[] args) throws InterruptedException { count.set(0); Thread[] threads=new Thread[10]; for (int i = 0; i < 10; i++) { Thread thread = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 1000; i++) { count.getAndIncrement(); } return; } }); thread.start(); threads[i]=thread; } // for(int i=0;i<10;i++) // { // threads[i].join(); // } Thread.sleep(1000);//等所有线程完成计算 System.out.println(count); } }
那这必须没问题啊。
赶紧肝CAS吧。。。
我们回到那个三部曲
(1)A=count
(2)B=A+1
(3)count=B
如果对于整个方法加synchronized或者reentrantLockk非常慢啊 我们的线程小子们都变成了串行的了。这还能叫多线程?
我们对于第三步(3)进行修改以及升级
(1)A=count
(2)B=A+1
(3)1.获得锁
2.获取count最新值new
3.CAS方法进行判断:是否当前count==A。相等则把B=count。不相等则循环等待相等的机会到来。
4.释放锁
public class cas { static volatile int count=0; public static int getCount(){return count;} public static synchronized boolean compareAndSwap(int exceptCount,int now){ if(getCount()==exceptCount) { count=now; return true; } else return false; } public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 10; i++) { Thread thread = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 1000; i++) { while(!compareAndSwap(count,count+1)){}//自旋 } return; } }); thread.start(); } Thread.sleep(1000);//等所有线程完成计算 System.out.println(count); } }
自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。
CAS的问题(ABA问题)
ABA问题的修改 对于比较的数据 变为一个pair 数据+版本号
5.悲观锁、乐观锁
悲观锁指的就是我们平常使用的加锁机制,它假设我们总是处于最坏的情况下,如果不加锁数据完整性就会被破坏。
而乐观锁指是一种基于冲突检测的方法,检测到冲突时操作就会失败。
这个情绪是描述操作系统对于资源的。
比如互斥锁 就是悲观锁,操作系统对于这个资源是悲观的,认为只要多个线程请求它,肯定会出错。
比如CAS就是乐观锁 他其实和锁并没有关系
6.原子类 atomicInteger的自增方法