![](https://img-blog.csdnimg.cn/img_convert/479e314b896c796b6b31d61c0c7e968d.png)
在我们日常生活中,事情应该一件一件的做,但是我们会感觉这样不仅效率低下,而且还特别累人,我们何不转换思想,看下图:
![](https://img-blog.csdnimg.cn/img_convert/1969d35286552833d41340b1c29ba407.png)
我们可以在烧开水的时候,顺便把洗茶具、拿茶叶的事情做了,这样是不是节省了好多的时间,所以并发顾名思义就是好多的线程一起在运行,那线程相关的知识,如果大家忘了,请看(线程详解)!!!
好了,言归正传,那什么是并发编程呢?
并行和并发
并行 :在同一个时间节点上,同时发生(是真正意义上的同时执行)
并发 :在一段时间内,对个事情交替执行
并发编程:在某些特定的场景中,有大量的请求访问同一个资源,会出现线程安全的问题,所以需要通过编程来控制解决让多个线程依次访问资源,称为并发。
并发的根本原因
以前单核CPU时,只能支持单线程模式,现在多核CPU,我们自然而然的可以支持多线程模式,在此之前,我们先浅谈下JMM(java内存模型)
java内存模型:它是java虚拟机规范的一种工作模式,将内存分为主内存和工作内存,变量数据存储在主内存中,线程在操作变量的时候会将主内存中的数据复制一份到工作内存,然后在工作内存中操作完后,再将数据写回主内存中。
![](https://img-blog.csdnimg.cn/img_convert/2bedfc437ba863c99204ed1008b92585.png)
多线程的出现,随之而来的也是与之对应的问题,下面我会为大家一一讲解!!!
![](https://img-blog.csdnimg.cn/img_convert/9248a3e50ac864d5bfea4db68345a974.png)
在比赛的两头,中间隔着巨高的墙体,两位选手都风快的向终点赶去,但是墙体之隔,使他们不知道对方的存在。
可见性:多个线程分别同时对共享数据操作,彼此之间不可见,操作完写回主内存,有可能出现问题
有序性:为了性能,虚拟机对一些代码指令的执行顺序进行重排,以提高速度
/*模拟指令重排序*/
public class Reorder {private static int x;private static int y;private static int a;private static int b;public static void main(String[] args) throws InterruptedException {int i = 0;for(;;) {i++;x = 0; y = 0;a = 0; b = 0;Thread one = new Thread(new Runnable() {public void run() {a = 1;x = b;}});Thread other = new Thread(new Runnable() {public void run() {b = 1;y = a;}});one.start();other.start();one.join();other.join();String result = "第" + i + "次 (" + x + "," + y + ")";if(x == 0 && y == 0) {System.err.println(result);break;} else {System.out.println(result);}}}
}
![](https://img-blog.csdnimg.cn/img_convert/6b5a4088c8b290dc5bfc4f1550b1692e.png)
在运行的过程中,系统先执行了后面的代码以至于跳出循环,这就是多线程出现的第二个问题无序性
原子性:一个或多个操作在 CPU 执行的过程中不被中断的特性
![](https://img-blog.csdnimg.cn/img_convert/ea8cb03b38741b431060f56dfa22d172.png)
两次count++,我们应该得到的是count=2,但是结果确实count=1,这就是多线程出现的问题之一(不可见性)
原因:
缓存(工作内存)带来了不可见性
指令重排优化带来了无序性
线程切换带来了非原子性
不可见变可见,无序变有序
让我们接下来介绍一个关键字volatile
底层实现原理:在底层指令级别进行控制,volatile修饰的变量在操作前,添加内存屏障,不让它的指令干扰,volatile修饰的变量添加内存屏障之外,还要通过缓存一致性协议(MESI)将数据写回到主内存,其他工作内存嗅探后,把自己的工作内存数据过期,重新从主内存读取最新的数据
不可见变可见:
public class TestVolatile {public static void main(String[] args) {//创建线程任务ThreadDemo td = new ThreadDemo();Thread t = new Thread(td);//创建线程t.start();//main线程中也需要使用flag变量while (true) {if (td.getFlag()) {//false-trueSystem.out.println("main---------------");break;}}}
}
public class ThreadDemo implements Runnable{/*volatile 修饰的变量,在一个线程中被修改后,对其它线程立即可见禁止cpu对指令重排序*/private (volatile)选加 boolean flag = false;//共享数据public void run() {try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}this.flag = true;//让一个线程修改共享变量值System.out.println(this.flag);}public boolean getFlag() {return flag;}public void setFlag(boolean flag) {this.flag = flag;}
}
没加volatile之前
![](https://img-blog.csdnimg.cn/img_convert/eded5ae244ba9cfc036d92d2747a5bd3.png)
当共享变量修改后,main方法并不知道变量已经修改,一致在重复着死循环
加volatile后
![](https://img-blog.csdnimg.cn/img_convert/cbc261af298af4f5ac1995cfcbf910d5.png)
当共享变量修改后,main方法立刻知道变量已经修改,跳出死循环
无序变有序😛见有序性的代码加以理解
原子性:
加锁
synchronized:关键字,可以修饰代码块、方法,自动获取、释放锁
ReentrantLock:类,只能对某段代码修饰,需要手动的进行加锁和释放锁
原子类
提供一些原子类,在低并发情况下使用,是一种无锁的实现,采用CAS机制
public class Atomic {//private static AtomicInteger atomicInteger = new AtomicInteger(0);private volatile static int num=0;public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(){@Overridepublic void run() {System.out.println(num++);try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}//System.out.println("线程"+(num++));//分为3条指令执行}}.start();}}
}
![](https://img-blog.csdnimg.cn/img_convert/ca78fe5b7c387321d9d42be13b4573a6.png)
这样会造成线程非顺序输出
public class Atomic {private static AtomicInteger atomicInteger = new AtomicInteger(0);
// / private volatile static int num=0;public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(){@Overridepublic void run() {System.out.println(atomicInteger.incrementAndGet());try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}//System.out.println("线程"+(num++));//分为3条指令执行}}.start();}}
}
![](https://img-blog.csdnimg.cn/img_convert/af053610e89d2b20432ed66ab99f2643.png)
利用了原子类就解决了刚才的问题
自旋思想(例如乐观锁):
![](https://img-blog.csdnimg.cn/img_convert/e88c38df04d699e53c7f3520373eed6e.png)
第一次获取内存值到工作内存中,存储起来作为预期值
然后对对象数据进行修改
将工作内存中值写入到主内存,在写入之前需要做一个判断,用预期值与主内存中的值进行比较
如果预期值与主内存值一致,说明没有其他线程进行修改,将更新的数据写入到主内存中
如果预期值与主内存中的值不一样,说明其他线程修改了主内存中的值,这时就需要重复操作整个过程
但是这样又会产生一个新的问题俗称(ABA)
![](https://img-blog.csdnimg.cn/img_convert/31c6e25617a468cfe2bf979ed422675a.png)
ABA 问题,即某个线程将内存值由 A 改为了 B,再由 B 改为了 A。当另外一个线程使用预期值去判断时,预期值与内存值相同,当前线程的 CAS 操作无法分辨当前 V 值是否发生过变化。
public class AtomicABA {public static void main(String[] args) throws InterruptedException {AtomicInteger atomicInteger = new AtomicInteger(100);//默认值为100new Thread(() -> {System.out.println(atomicInteger.compareAndSet(100, 101));//设置预期值是100 修改值为101System.out.println(atomicInteger.get());System.out.println(atomicInteger.compareAndSet(101, 100));//设置预期值是101 修改值为100System.out.println(atomicInteger.get());}).start();Thread.sleep(1000);new Thread(() -> {System.out.println(atomicInteger.compareAndSet(100, 101));//返回true 说明修改成功 发生ABA问题System.out.println(atomicInteger.get());}).start();}
}
![](https://img-blog.csdnimg.cn/img_convert/82b5e0453d992ef25d97b1652159a3d3.png)
import java.util.concurrent.atomic.AtomicStampedReference;public class AtomicABA1 {public static void main(String[] args) throws InterruptedException {AtomicStampedReference stampedReference = new AtomicStampedReference(100, 0);new Thread(() -> {try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp() + 1);stampedReference.compareAndSet(101,100,stampedReference.getStamp(), stampedReference.getStamp() + 1);}).start();new Thread(() -> {int stamp = stampedReference.getStamp();try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}boolean result = stampedReference.compareAndSet(100, 101, stamp, stamp + 1);System.out.println(" >>> 修改 stampedReference :: %s "+result);}).start();}}
![](https://img-blog.csdnimg.cn/img_convert/83d18c0552effddf1f027e42ea19de8d.png)
CAS缺点:超多线程一同访问时,不停自旋判断,导致cpu占用率高.