第一、并发编程三大个性
原子性:指一系列操作是不可分割的,一旦执行则整个过程将会一次性全副执行实现,不会停留在中间状态。(有点相似于事务的概念)。
举例:例如A向B汇款1000元,那么就须要有两个操作,一个是A账户减1000元,另一个是B账户减少1000元,如果这个过程中任何一个操作呈现故障,都是不合乎规矩的也是不能保障汇款人和收款人的财产平安。换句话说,如果想要保障每次转账都不会造成单方任何一方的财产损失,咱们必须要保障操作的原子性。要么都做,要么都不做。
可见性:多个线程拜访同一共享数据的时候,如果某一个线程批改了此共享数据,那么其余线程可能立刻看到此数据的扭转。即批改可见。
有序性:代码执行时的程序与语句程序统一。也就是说执行前不重排。指令重排序不会影响单个线程的执行,然而会影响到线程并发执行的正确性
要想并发程序正确地执行,必须要保障原子性、可见性以及有序性。只有有一个没有被保障,就有可能会导致程序运行不正确。
第二、CAS
定义一个账户接口:Account .java
import java.util.ArrayList;import java.util.List;/** * 定义一个账户接口 * @author shixiangcheng * 2019-12-17 */public interface Account { //获取余额 Integer getBalance(); //取款 void withdraw(Integer amount); //办法内启动1000个线程,每个线程做-10元的操作。若初始余额为10000,那么正确后果该当为0 static void demo(Account account) { List<Thread> ts=new ArrayList<Thread> (); long start=System.currentTimeMillis(); for(int i=0;i<1000;i++) { ts.add(new Thread(()->{ account.withdraw(10); })); } ts.forEach(Thread::start); ts.forEach(t->{ try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } }); long end=System.currentTimeMillis(); System.out.println(account.getBalance()+" cost: "+(end-start)+"ms"); }}
2.接口实现类AccountImpl.java
import java.util.concurrent.atomic.AtomicInteger;/** * 接口实现类 * @author shixiangcheng * 2019-12-17 */public class AccountImpl implements Account { private AtomicInteger balance;//账户余额 public AccountImpl(int balance) {//通过构造方法给一个默认的余额 this.balance = new AtomicInteger(balance); } @Override public Integer getBalance() { return balance.get(); } @Override public void withdraw(Integer amount) { //一直尝试,直到胜利为止 while(true) { int prev=balance.get(); int next=prev-amount; /**比拟替换:在set前先比拟prev和以后值? * 不统一,next作废,cas返回false标识失败 * 统一,以next设置为新值,返回true标识胜利 */ if(balance.compareAndSet(prev, next)) { break; } } }}
测试类Test.java
/** * 测试类 * @author shixiangcheng * 2019-12-17 */public class Test { public static void main(String [] args) { Account.demo(new AccountImpl(10000)); }}
测试后果0 cost: 131ms
总结:compareAndSet的简称就是CAS,它必须是原子操作。CAS的底层是lock cmpxchg指令(X86架构),在单核CPU和多核CPU下都可能保障比拟-替换的原子性。在多核状态下,某个核执行到带lock的指令时,CPU会让总线锁住,当这个核把此指令执行结束,再开启总线,这个过程中不会被线程的调度机制所打断,保障了多个线程对内存操作的准确性,是原子的。
第三、Volatile
/** * 测试可见性 * @author shixiangcheng * 2019-12-17 */public class TestVolatile{ static boolean run=true; static long i=0; public static void main(String [] args) throws InterruptedException { System.out.println("A"); Thread t=new Thread(()-> { System.out.println("1"); while(run) { i++; } System.out.println("2"); }); t.start(); Thread.sleep(1000); run=false; System.out.println("B"); }}
浏览代码揣测,程序执行后果输入:A 1 B 2,然而理论执行后执行后果:
从执行后果看,右上角还有一个红色的标识,标识代码没有执行完结。t线程没有完结,而是始终在while中循环,因为其并没有感知到run的值曾经被批改了。也就是主线程对共享变量的批改,对其它线程不可见。这将会造成线程不平安。
java线程内存模型如下:
每个线程有独立的工作内存(寄存器和高速缓存合称工作内存),为进步执行效率,线程会将数据从主存复制一份到工作内存,线程操作的是本人的工作内存,而不会间接操作主内存。如果线程对变量的操作没有刷写会主内存的话,仅仅扭转了本人的工作内存的变量的正本,那么对于其余线程来说是不可见的。而如果另一个变量没有读取主内存中的新的值,而是应用旧的值的话,同样的也能够列为不可见。对于jvm来说,主内存是所有线程共享的java堆,而工作内存中的共享变量的正本是从主内存拷贝过来的,是线程公有的局部变量,位于java栈中。
解决方案:static volatile boolean run=true;
在JVM底层volatile是采纳“内存屏障”来实现的。察看退出volatile关键字和没有退出volatile关键字时所生成的汇编代码发现,退出volatile关键字时,会多出一个lock前缀指令,lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),
**内存屏障会提供3个性能:
1.它确保指令重排序时不会把其前面的指令排到内存屏障之前的地位,也不会把后面的指令排到内存屏障的前面;即在执行到内存屏障这句指令时,在它后面的操作曾经全副实现;
2.它会强制将对缓存的批改操作立刻写入主存;
- 如果是写操作,它会导致其余CPU中对应的缓存行有效。
第四、CAS的特点**
联合CAS和volatile能够实现无锁并发,实用于竞争不强烈,多核CPU的场景下。
CAS是基于乐观锁的思维。
synchronized是基于乐观锁的思维。
CAS体现的是无锁并发,无阻塞并发。
因为没有synchronized,所以线程不会陷入阻塞,这是效率晋升的因素之一。但如果竞争强烈,能够想到重试必然频繁产生,反而效率会受到影响。
第五、为什么无锁效率高
无锁状况下,即便重试失败,线程始终在高速运行,没有停歇,而synchronized会让线程在没有取得锁的时候,产生上下文切换,进入阻塞。 但无锁状况下,因为线程要放弃运行,须要额定CPU反对,如果没有,线程高速运行也就无从谈起,尽管不会进入阻塞,但因为没有分到工夫片,依然会进入可运行状态,还是会导致上下文切换。
留神:应用CAS时,线程数不能够超过CPU的外围数。